no commit message

This commit is contained in:
niandeng
2020-09-17 18:40:01 +08:00
parent 4382915d09
commit d957abceaa
283 changed files with 86935 additions and 63 deletions
@@ -0,0 +1,484 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mangui.hls.stream {
import org.mangui.hls.constant.HLSPlayStates;
import org.mangui.hls.constant.HLSSeekStates;
import org.mangui.hls.controller.BufferThresholdController;
import org.mangui.hls.demux.ID3Tag;
import org.mangui.hls.event.HLSError;
import org.mangui.hls.event.HLSEvent;
import org.mangui.hls.event.HLSPlayMetrics;
import org.mangui.hls.flv.FLVTag;
import org.mangui.hls.HLS;
import org.mangui.hls.HLSSettings;
//import by.blooddy.crypto.Base64;
import com.hurlant.util.Base64;
import flash.events.Event;
import flash.events.NetStatusEvent;
import flash.events.TimerEvent;
import flash.net.NetConnection;
import flash.net.NetStream;
import flash.net.NetStreamAppendBytesAction;
import flash.net.NetStreamInfo;
import flash.net.NetStreamPlayOptions;
import flash.utils.ByteArray;
import flash.utils.Timer;
CONFIG::LOGGING {
import org.mangui.hls.utils.Log;
}
/** Class that overrides standard flash.net.NetStream class, keeps the buffer filled, handles seek and play state
*
* play state transition :
* FROM TO condition
* HLSPlayStates.IDLE HLSPlayStates.PLAYING_BUFFERING idle => play()/play2() called
* HLSPlayStates.IDLE HLSPlayStates.PAUSED_BUFFERING idle => seek() called
* HLSPlayStates.PLAYING_BUFFERING HLSPlayStates.PLAYING buflen > minBufferLength
* HLSPlayStates.PAUSED_BUFFERING HLSPlayStates.PAUSED buflen > minBufferLength
* HLSPlayStates.PLAYING HLSPlayStates.PLAYING_BUFFERING buflen < lowBufferLength
* HLSPlayStates.PAUSED HLSPlayStates.PAUSED_BUFFERING buflen < lowBufferLength
*
* seek state transition :
*
* FROM TO condition
* HLSSeekStates.IDLE/SEEKED HLSSeekStates.SEEKING play()/play2()/seek() called
* HLSSeekStates.SEEKING HLSSeekStates.SEEKED upon first FLV tag appending after seek
* HLSSeekStates.SEEKED HLSSeekStates.IDLE upon playback complete or stop() called
*/
public class HLSNetStream extends NetStream {
/** Reference to the framework controller. **/
private var _hls : HLS;
/** reference to buffer threshold controller */
private var _bufferThresholdController : BufferThresholdController;
/** FLV Tag Buffer . **/
private var _streamBuffer : StreamBuffer;
/** Timer used to check buffer and position. **/
private var _timer : Timer;
/** Current playback state. **/
private var _playbackState : String;
/** Current seek state. **/
private var _seekState : String;
/** current playback level **/
private var _currentLevel : int;
/** Netstream client proxy */
private var _client : HLSNetStreamClient;
/** skipped fragment duration **/
private var _skippedDuration : Number;
/** watched duration **/
private var _watchedDuration : Number;
/** dropped frames counter **/
private var _droppedFrames : Number;
/** last NetStream.time, used to check if playback is over **/
private var _lastNetStreamTime : Number;
/** Create the buffer. **/
public function HLSNetStream(connection : NetConnection, hls : HLS, streamBuffer : StreamBuffer) : void {
super(connection);
super.bufferTime = 0.1;
_hls = hls;
_skippedDuration = _watchedDuration = _droppedFrames = _lastNetStreamTime = 0;
_bufferThresholdController = new BufferThresholdController(hls);
_streamBuffer = streamBuffer;
_playbackState = HLSPlayStates.IDLE;
_seekState = HLSSeekStates.IDLE;
_timer = new Timer(100, 0);
_timer.addEventListener(TimerEvent.TIMER, _checkBuffer);
_client = new HLSNetStreamClient();
_client.registerCallback("onHLSFragmentChange", onHLSFragmentChange);
_client.registerCallback("onHLSFragmentSkipped", onHLSFragmentSkipped);
_client.registerCallback("onID3Data", onID3Data);
super.client = _client;
}
public function onHLSFragmentChange(level : int, seqnum : int, cc : int, duration : Number, audio_only : Boolean, program_date : Number, width : int, height : int, auto_level : Boolean, customTagNb : int, id3TagNb : int, ... tags) : void {
CONFIG::LOGGING {
Log.debug("playing fragment(level/sn/cc):" + level + "/" + seqnum + "/" + cc);
}
_currentLevel = level;
var customTagArray : Array = new Array();
var id3TagArray : Array = new Array();
for (var i : uint = 0; i < customTagNb; i++) {
customTagArray.push(tags[i]);
CONFIG::LOGGING {
Log.debug("custom tag:" + tags[i]);
}
}
for (i = customTagNb; i < tags.length; i+=4) {
var id3Tag : ID3Tag = new ID3Tag(tags[i],tags[i+1],tags[i+2],tags[i+3]);
id3TagArray.push(id3Tag);
CONFIG::LOGGING {
Log.debug("id3 tag:" + id3Tag);
}
}
_hls.dispatchEvent(new HLSEvent(HLSEvent.FRAGMENT_PLAYING, new HLSPlayMetrics(level, seqnum, cc, duration, audio_only, program_date, width, height, auto_level, customTagArray,id3TagArray)));
}
public function onHLSFragmentSkipped(level : int, seqnum : int,duration : Number) : void {
CONFIG::LOGGING {
Log.warn("skipped fragment(level/sn/duration):" + level + "/" + seqnum + "/" + duration);
}
_skippedDuration+=duration;
_hls.dispatchEvent(new HLSEvent(HLSEvent.FRAGMENT_SKIPPED, duration));
}
// function is called by SCRIPT in FLV
public function onID3Data(data : ByteArray) : void {
// we dump the content as base64 to get it to the Javascript in the browser.
// The client can use window.atob() to decode the ID3Data.
var dump : String = Base64.encodeByteArray(data);
CONFIG::LOGGING {
Log.debug("id3:" + dump);
}
_hls.dispatchEvent(new HLSEvent(HLSEvent.ID3_UPDATED, dump));
}
/** timer function, check/update NetStream state, and append tags if needed **/
private function _checkBuffer(e : Event) : void {
var buffer : Number = this.bufferLength,
minBufferLength : Number =_bufferThresholdController.minBufferLength,
reachedEnd : Boolean = _streamBuffer.reachedEnd,
liveLoadingStalled : Boolean = _streamBuffer.liveLoadingStalled;
// Log.info("netstream/total:" + super.bufferLength + "/" + this.bufferLength);
if (_seekState != HLSSeekStates.SEEKING) {
if (_playbackState == HLSPlayStates.PLAYING || _playbackState == HLSPlayStates.PLAYING_BUFFERING) {
/* check if play head reached end of stream.
this happens when
playstate is PLAYING
AND last fragment has been loaded,
either because we reached end of VOD or because live loading stalled ...
AND NetStream is almost empty(less than 2s ... this is just for safety ...)
AND StreamBuffer is empty(it means that last fragment tags have been appended in NetStream)
AND playhead is not moving anymore (NetStream.time not changing overtime)
*/
if((reachedEnd || liveLoadingStalled) &&
bufferLength <= 2 &&
_streamBuffer.bufferLength == 0 &&
_lastNetStreamTime &&
super.time == _lastNetStreamTime) {
// playhead is not moving anymore ... append sequence end.
super.appendBytesAction(NetStreamAppendBytesAction.END_SEQUENCE);
super.appendBytes(new ByteArray());
// have we reached end of playlist ?
if(reachedEnd) {
// stop timer, report event and switch to IDLE mode.
_timer.stop();
CONFIG::LOGGING {
Log.debug("reached end of VOD playlist, notify playback complete");
}
_hls.dispatchEvent(new HLSEvent(HLSEvent.PLAYBACK_COMPLETE));
_setPlaybackState(HLSPlayStates.IDLE);
_setSeekState(HLSSeekStates.IDLE);
} else {
// live loading stalled : flush buffer and restart playback
CONFIG::LOGGING {
Log.warn("loading stalled: restart playback");
}
// flush whole buffer before seeking
_streamBuffer.flushBuffer();
/* seek to force a restart of the playback session */
seek(-1);
}
return;
} else if (buffer <= 0.1 && !reachedEnd) {
// playing and buffer <= 0.1 and not reachedEnd and not EOS, pause playback
super.pause();
// low buffer condition and play state. switch to play buffering state
_setPlaybackState(HLSPlayStates.PLAYING_BUFFERING);
}
_lastNetStreamTime = super.time;
}
// if buffer len is below lowBufferLength, get into buffering state
if (!reachedEnd && !liveLoadingStalled && buffer < _bufferThresholdController.lowBufferLength) {
if (_playbackState == HLSPlayStates.PLAYING) {
// low buffer condition and play state. switch to play buffering state
_setPlaybackState(HLSPlayStates.PLAYING_BUFFERING);
} else if (_playbackState == HLSPlayStates.PAUSED) {
// low buffer condition and pause state. switch to paused buffering state
_setPlaybackState(HLSPlayStates.PAUSED_BUFFERING);
}
}
// if buffer len is above minBufferLength, get out of buffering state
if (buffer >= minBufferLength || reachedEnd || liveLoadingStalled) {
if (_playbackState == HLSPlayStates.PLAYING_BUFFERING) {
CONFIG::LOGGING {
Log.debug("resume playback, minBufferLength/bufferLength:"+minBufferLength.toFixed(2) + "/" + buffer.toFixed(2));
}
// resume playback in case it was paused, this can happen if buffer was in really low condition (less than 0.1s)
super.resume();
_setPlaybackState(HLSPlayStates.PLAYING);
} else if (_playbackState == HLSPlayStates.PAUSED_BUFFERING) {
_setPlaybackState(HLSPlayStates.PAUSED);
}
}
}
}
/** Return the current playback state. **/
public function get playbackState() : String {
return _playbackState;
}
/** Return the current seek state. **/
public function get seekState() : String {
return _seekState;
}
/** Return the current playback quality level **/
public function get currentLevel() : int {
return _currentLevel;
}
/** append tags to NetStream **/
public function appendTags(tags : Vector.<FLVTag>) : void {
if (_seekState == HLSSeekStates.SEEKING) {
/* this is our first injection after seek(),
let's flush netstream now
this is to avoid black screen during seek command */
_watchedDuration += super.time;
_droppedFrames += super.info.droppedFrames;
_skippedDuration = 0;
super.close();
// useHardwareDecoder was added in FP11.1, but this allows us to include the option in all builds
try {
super['useHardwareDecoder'] = HLSSettings.useHardwareVideoDecoder;
} catch(e : Error) {
// Ignore errors, we're running in FP < 11.1
}
super.play(null);
super.appendBytesAction(NetStreamAppendBytesAction.RESET_SEEK);
// immediatly pause NetStream, it will be resumed when enough data will be buffered in the NetStream
super.pause();
// var otherCounter : int = 0;
// for each (var tagBuffer0 : FLVTag in tags) {
// switch(tagBuffer0.type) {
// case FLVTag.AAC_HEADER:
// case FLVTag.AVC_HEADER:
// case FLVTag.DISCONTINUITY:
// case FLVTag.METADATA:
// CONFIG::LOGGING {
// Log.info('inject type/dts/pts:' + tagBuffer0.typeString + '/' + tagBuffer0.dts + '/' + tagBuffer0.pts);
// }
// break;
// default:
// CONFIG::LOGGING {
// if(otherCounter++< 5) {
// Log.info('inject type/dts/pts:' + tagBuffer0.typeString + '/' + tagBuffer0.dts + '/' + tagBuffer0.pts);
// }
// }
// break;
// }
// }
}
// append all tags
//var otherCounter : int = 0;
for each (var tagBuffer : FLVTag in tags) {
// switch(tagBuffer.type) {
// case FLVTag.AAC_HEADER:
// case FLVTag.AVC_HEADER:
// case FLVTag.DISCONTINUITY:
// case FLVTag.METADATA:
// otherCounter = 0;
// CONFIG::LOGGING {
// Log.info('inject type/dts/pts:' + tagBuffer.typeString + '/' + tagBuffer.dts + '/' + tagBuffer.pts);
// }
// break;
// default:
// CONFIG::LOGGING {
// if(otherCounter++< 5) {
// Log.info('inject type/dts/pts:' + tagBuffer.typeString + '/' + tagBuffer.dts + '/' + tagBuffer.pts);
// }
// }
// break;
// }
// CONFIG::LOGGING {
// Log.debug2('inject type/dts/pts:' + tagBuffer.typeString + '/' + tagBuffer.dts + '/' + tagBuffer.pts);
// }
try {
if (tagBuffer.type == FLVTag.DISCONTINUITY) {
super.appendBytesAction(NetStreamAppendBytesAction.RESET_BEGIN);
super.appendBytes(FLVTag.getHeader());
}
super.appendBytes(tagBuffer.data);
} catch (error : Error) {
var hlsError : HLSError = new HLSError(HLSError.TAG_APPENDING_ERROR, null, tagBuffer.type + ": " + error.message);
_hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, hlsError));
}
}
if (_seekState == HLSSeekStates.SEEKING) {
// dispatch event to mimic NetStream behaviour
dispatchEvent(new NetStatusEvent(NetStatusEvent.NET_STATUS, false, false, {code:"NetStream.Seek.Notify", level:"status"}));
_setSeekState(HLSSeekStates.SEEKED);
}
}
/** Change playback state. **/
private function _setPlaybackState(state : String) : void {
if (state != _playbackState) {
CONFIG::LOGGING {
Log.debug('[PLAYBACK_STATE] from ' + _playbackState + ' to ' + state);
}
_playbackState = state;
_hls.dispatchEvent(new HLSEvent(HLSEvent.PLAYBACK_STATE, _playbackState));
}
}
/** Change seeking state. **/
private function _setSeekState(state : String) : void {
if (state != _seekState) {
CONFIG::LOGGING {
Log.debug('[SEEK_STATE] from ' + _seekState + ' to ' + state);
}
_seekState = state;
_hls.dispatchEvent(new HLSEvent(HLSEvent.SEEK_STATE, _seekState));
}
}
/* also include skipped duration in get time() so that play position will match fragment position */
override public function get time() : Number {
return super.time+_skippedDuration;
}
/* return nb of dropped Frames since session started */
public function get droppedFrames() : Number {
return super.info.droppedFrames + _droppedFrames;
}
/** Return total watched time **/
public function get watched() : Number {
return super.time + _watchedDuration;
}
override public function play(...args) : void {
var _playStart : Number;
if (args.length >= 2) {
_playStart = Number(args[1]);
} else {
_playStart = -1;
}
CONFIG::LOGGING {
Log.info("HLSNetStream:play(" + _playStart + ")");
}
seek(_playStart);
_setPlaybackState(HLSPlayStates.PLAYING_BUFFERING);
}
override public function play2(param : NetStreamPlayOptions) : void {
CONFIG::LOGGING {
Log.info("HLSNetStream:play2(" + param.start + ")");
}
seek(param.start);
_setPlaybackState(HLSPlayStates.PLAYING_BUFFERING);
}
/** Pause playback. **/
override public function pause() : void {
CONFIG::LOGGING {
Log.info("HLSNetStream:pause");
}
if (_playbackState == HLSPlayStates.PLAYING) {
super.pause();
_setPlaybackState(HLSPlayStates.PAUSED);
} else if (_playbackState == HLSPlayStates.PLAYING_BUFFERING) {
super.pause();
_setPlaybackState(HLSPlayStates.PAUSED_BUFFERING);
}
}
/** Resume playback. **/
override public function resume() : void {
CONFIG::LOGGING {
Log.info("HLSNetStream:resume");
}
if (_playbackState == HLSPlayStates.PAUSED) {
super.resume();
_setPlaybackState(HLSPlayStates.PLAYING);
} else if (_playbackState == HLSPlayStates.PAUSED_BUFFERING) {
// dont resume NetStream here, it will be resumed by Timer. this avoids resuming playback while seeking is in progress
_setPlaybackState(HLSPlayStates.PLAYING_BUFFERING);
}
}
/** get Buffer Length **/
override public function get bufferLength() : Number {
return netStreamBufferLength + _streamBuffer.bufferLength;
}
/** get Back Buffer Length **/
override public function get backBufferLength() : Number {
return _streamBuffer.backBufferLength;
}
public function get netStreamBufferLength() : Number {
if (_seekState == HLSSeekStates.SEEKING) {
return 0;
} else {
return super.bufferLength;
}
}
/** Start playing data in the buffer. **/
override public function seek(position : Number) : void {
CONFIG::LOGGING {
Log.info("HLSNetStream:seek(" + position + ")");
}
_streamBuffer.seek(position);
_setSeekState(HLSSeekStates.SEEKING);
/* if HLS playback state was in PAUSED or IDLE state before seeking,
* switch to paused buffering state
* otherwise, switch to playing buffering state
*/
switch(_playbackState) {
case HLSPlayStates.IDLE:
case HLSPlayStates.PAUSED:
case HLSPlayStates.PAUSED_BUFFERING:
_setPlaybackState(HLSPlayStates.PAUSED_BUFFERING);
break;
case HLSPlayStates.PLAYING:
case HLSPlayStates.PLAYING_BUFFERING:
_setPlaybackState(HLSPlayStates.PLAYING_BUFFERING);
break;
default:
break;
}
/* always pause NetStream while seeking, even if we are in play state
* in that case, NetStream will be resumed during next call to appendTags()
*/
super.pause();
_timer.start();
}
public override function set client(client : Object) : void {
_client.delegate = client;
}
public override function get client() : Object {
return _client.delegate;
}
/** Stop playback. **/
override public function close() : void {
CONFIG::LOGGING {
Log.info("HLSNetStream:close");
}
super.close();
_watchedDuration = _skippedDuration = _lastNetStreamTime = _droppedFrames = 0;
_streamBuffer.stop();
_timer.stop();
_setPlaybackState(HLSPlayStates.IDLE);
_setSeekState(HLSSeekStates.IDLE);
}
public function dispose_() : void {
close();
_timer.removeEventListener(TimerEvent.TIMER, _checkBuffer);
_bufferThresholdController.dispose();
}
}
}
@@ -0,0 +1,61 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mangui.hls.stream {
import flash.utils.flash_proxy;
import flash.utils.Proxy;
/** Proxy that allows dispatching internal events fired by Netstream cues to
* internal listeners as well as the traditional client object
*/
public class HLSNetStreamClient extends Proxy {
private var _delegate : Object;
private var _callbacks : Object = new Object();
public function HLSNetStreamClient() {
}
public function set delegate(client : Object) : void {
this._delegate = client;
}
public function get delegate() : Object {
return this._delegate;
}
public function registerCallback(name : String, callback : Function) : void {
_callbacks[name] = callback;
}
override flash_proxy function callProperty(methodName : *, ... args) : * {
var r : * = null;
if (_callbacks && _callbacks.hasOwnProperty(methodName)) {
r = _callbacks[methodName](args);
}
if (_delegate && _delegate.hasOwnProperty(methodName)) {
r = _delegate[methodName](args);
}
return r;
}
override flash_proxy function getProperty(name : *) : * {
var r : *;
if (_callbacks && _callbacks.hasOwnProperty(name)) {
r = _callbacks[name];
}
if (_delegate && _delegate.hasOwnProperty(name)) {
r = _delegate[name];
}
return r;
}
override flash_proxy function hasProperty(name : *) : Boolean {
return (_delegate && _delegate.hasOwnProperty(name)) || _callbacks.hasOwnProperty(name);
}
}
}
File diff suppressed because it is too large Load Diff