diff --git a/src/org/mangui/chromeless/ChromelessPlayer.as b/src/org/mangui/chromeless/ChromelessPlayer.as index 32a1ebe2..54a97dfc 100644 --- a/src/org/mangui/chromeless/ChromelessPlayer.as +++ b/src/org/mangui/chromeless/ChromelessPlayer.as @@ -222,6 +222,18 @@ package org.mangui.chromeless { _trigger("switch", event.level); }; + protected function _fpsDropHandler(event : HLSEvent) : void { + _trigger("fpsDrop", event.level); + }; + + protected function _fpsDropLevelCappingHandler(event : HLSEvent) : void { + _trigger("fpsDropLevelCapping", event.level); + }; + + protected function _fpsDropSmoothLevelSwitchHandler(event : HLSEvent) : void { + _trigger("fpsDropSmoothLevelSwitch"); + }; + protected function _audioTracksListChange(event : HLSEvent) : void { _trigger("audioTracksListChange", _getAudioTrackList()); } @@ -495,6 +507,9 @@ package org.mangui.chromeless { _hls.addEventListener(HLSEvent.AUDIO_TRACKS_LIST_CHANGE, _audioTracksListChange); _hls.addEventListener(HLSEvent.AUDIO_TRACK_SWITCH, _audioTrackChange); _hls.addEventListener(HLSEvent.ID3_UPDATED, _id3Updated); + _hls.addEventListener(HLSEvent.FPS_DROP, _fpsDropHandler); + _hls.addEventListener(HLSEvent.FPS_DROP_LEVEL_CAPPING, _fpsDropLevelCappingHandler); + _hls.addEventListener(HLSEvent.FPS_DROP_SMOOTH_LEVEL_SWITCH, _fpsDropSmoothLevelSwitchHandler); if (available && stage.stageVideos.length > 0) { _stageVideo = stage.stageVideos[0]; diff --git a/src/org/mangui/hls/HLS.as b/src/org/mangui/hls/HLS.as index 74a337fb..9905a5f3 100644 --- a/src/org/mangui/hls/HLS.as +++ b/src/org/mangui/hls/HLS.as @@ -10,6 +10,7 @@ package org.mangui.hls { import flash.net.NetStream; import flash.net.URLLoader; import flash.net.URLStream; + import org.mangui.hls.constant.HLSSeekStates; import org.mangui.hls.controller.AudioTrackController; import org.mangui.hls.controller.LevelController; import org.mangui.hls.event.HLSEvent; @@ -135,8 +136,11 @@ package org.mangui.hls { /* instant quality level switch (-1 for automatic level selection) */ public function set currentLevel(level : int) : void { _manual_level = level; - _streamBuffer.flushBuffer(); - _hlsNetStream.seek(position); + // don't flush and seek if never seeked or if end of stream + if(seekState != HLSSeekStates.IDLE) { + _streamBuffer.flushBuffer(); + _hlsNetStream.seek(position); + } }; /* set quality level for next loaded fragment (-1 for automatic level selection) */ @@ -180,6 +184,16 @@ package org.mangui.hls { return _streamBuffer.position; }; + /** Return the live main playlist sliding in seconds since previous out of buffer seek(). **/ + public function get liveSlidingMain() : Number { + return _streamBuffer.liveSlidingMain; + } + + /** Return the live altaudio playlist sliding in seconds since previous out of buffer seek(). **/ + public function get liveSlidingAltAudio() : Number { + return _streamBuffer.liveSlidingAltAudio; + } + /** Return the current playback state. **/ public function get playbackState() : String { return _hlsNetStream.playbackState; @@ -269,5 +283,13 @@ package org.mangui.hls { public function get stats() : Stats { return _statsHandler.stats; } + + /* start/restart playlist/fragment loading. + this is only effective if MANIFEST_PARSED event has been triggered already */ + public function startLoad() : void { + if(levels && levels.length) { + this.dispatchEvent(new HLSEvent(HLSEvent.LEVEL_SWITCH, startLevel)); + } + } } } diff --git a/src/org/mangui/hls/HLSSettings.as b/src/org/mangui/hls/HLSSettings.as index ddd65af8..a1b58b18 100644 --- a/src/org/mangui/hls/HLSSettings.as +++ b/src/org/mangui/hls/HLSSettings.as @@ -6,6 +6,19 @@ package org.mangui.hls { import org.mangui.hls.constant.HLSMaxLevelCappingMode; public final class HLSSettings extends Object { + /** + * autoStartLoad + * + * if set to true, + * start level playlist and first fragments will be loaded automatically, + * after triggering of HlsEvent.MANIFEST_PARSED event + * if set to false, + * an explicit API call (hls.startLoad()) will be needed + * to start quality level/fragment loading. + * + * Default is true + */ + public static var autoStartLoad : Boolean = true; /** * capLevelToStage * diff --git a/src/org/mangui/hls/controller/LevelController.as b/src/org/mangui/hls/controller/LevelController.as index 2ea389e3..528a7ec6 100644 --- a/src/org/mangui/hls/controller/LevelController.as +++ b/src/org/mangui/hls/controller/LevelController.as @@ -42,7 +42,9 @@ package org.mangui.hls.controller { public function LevelController(hls : HLS) : void { _hls = hls; _fpsController = new FPSController(hls); - _hls.addEventListener(HLSEvent.MANIFEST_PARSED, _manifestParsedHandler); + /* low priority listener, so that other listeners with default priority + could seamlessly set hls.startLevel in their HLSEvent.MANIFEST_PARSED listener */ + _hls.addEventListener(HLSEvent.MANIFEST_PARSED, _manifestParsedHandler, false, int.MIN_VALUE); _hls.addEventListener(HLSEvent.MANIFEST_LOADED, _manifestLoadedHandler); _hls.addEventListener(HLSEvent.FRAGMENT_LOADED, _fragmentLoadedHandler); _hls.addEventListener(HLSEvent.FRAGMENT_LOAD_EMERGENCY_ABORTED, _fragmentLoadedHandler); @@ -69,8 +71,10 @@ package org.mangui.hls.controller { } private function _manifestParsedHandler(event : HLSEvent) : void { - // upon manifest parsed event, trigger a level switch to load startLevel playlist - _hls.dispatchEvent(new HLSEvent(HLSEvent.LEVEL_SWITCH, _hls.startLevel)); + if(HLSSettings.autoStartLoad) { + // upon manifest parsed event, trigger a level switch to load startLevel playlist + _hls.dispatchEvent(new HLSEvent(HLSEvent.LEVEL_SWITCH, startLevel)); + } } private function _manifestLoadedHandler(event : HLSEvent) : void { @@ -292,6 +296,9 @@ package org.mangui.hls.controller { return 0; } + public function isStartLevelSet() : Boolean { + return (_startLevel >=0); + } /* set the quality level used when starting a fresh playback */ public function set startLevel(level : int) : void { @@ -371,8 +378,8 @@ package org.mangui.hls.controller { var seek_level : int = -1; var levels : Vector. = _hls.levels; if (HLSSettings.seekFromLevel == -1) { - // keep last level - return _hls.loadLevel; + // keep last level, but don't exceed _maxLevel + return Math.min(_hls.loadLevel,_maxLevel); } // set up seek level as being the lowest non-audio level. diff --git a/src/org/mangui/hls/loader/FragmentLoader.as b/src/org/mangui/hls/loader/FragmentLoader.as index bc607db9..9e29bb93 100644 --- a/src/org/mangui/hls/loader/FragmentLoader.as +++ b/src/org/mangui/hls/loader/FragmentLoader.as @@ -203,7 +203,13 @@ package org.mangui.hls.loader { if (_manifestJustLoaded) { level = _hls.startLevel; } else { - level = _hls.seekLevel; + if(_hls.stream.bufferLength) { + // if buffer not empty, select level from heuristics + level = _levelController.getnextlevel(_hls.loadLevel, _hls.stream.bufferLength); + } else { + // if buffer empty, retrieve seek level + level = _hls.seekLevel; + } } } else { level = _hls.manualLevel; @@ -317,6 +323,7 @@ package org.mangui.hls.loader { _fragmentFirstLoaded = false; _fragPrevious = null; _fragSkipping = false; + _levelNext = -1; _timer.start(); } @@ -927,6 +934,7 @@ package org.mangui.hls.loader { return; var hlsError : HLSError; var fragData : FragmentData = _fragCurrent.data; + var fragLevelIdx : int = _fragCurrent.level; if ((_demux.audioExpected && !fragData.audio_found) && (_demux.videoExpected && !fragData.video_found)) { hlsError = new HLSError(HLSError.FRAGMENT_PARSING_ERROR, _fragCurrent.url, "error parsing fragment, no tag found"); _hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, hlsError)); @@ -953,12 +961,12 @@ package org.mangui.hls.loader { if (_manifestJustLoaded) { _manifestJustLoaded = false; - if (HLSSettings.startFromLevel === -1 && HLSSettings.startFromBitrate === -1 && _levels.length > 1) { + if (HLSSettings.startFromLevel === -1 && HLSSettings.startFromBitrate === -1 && _levels.length > 1 && !_levelController.isStartLevelSet()) { // check if we can directly switch to a better bitrate, in case download bandwidth is enough var bestlevel : int = _levelController.getbestlevel(_metrics.bandwidth); - if (bestlevel > _hls.loadLevel) { + if (bestlevel > fragLevelIdx) { CONFIG::LOGGING { - Log.info("enough download bandwidth, adjust start level from " + _hls.loadLevel + " to " + bestlevel); + Log.info("enough download bandwidth, adjust start level from 0 to " + bestlevel); } // dispatch event for tracking purpose _hls.dispatchEvent(new HLSEvent(HLSEvent.FRAGMENT_LOADED, _metrics)); @@ -967,7 +975,7 @@ package org.mangui.hls.loader { _loadingState = LOADING_IDLE; _switchLevel = true; _demux = null; - _hls.dispatchEvent(new HLSEvent(HLSEvent.LEVEL_SWITCH, _hls.loadLevel)); + _hls.dispatchEvent(new HLSEvent(HLSEvent.LEVEL_SWITCH, fragLevelIdx)); // speed up loading of new playlist _timer.start(); return; @@ -978,14 +986,15 @@ package org.mangui.hls.loader { try { _switchLevel = false; _levelNext = -1; + var fragLevel : Level = _levels[fragLevelIdx]; CONFIG::LOGGING { - Log.debug("Loaded " + _fragCurrent.seqnum + " of [" + (_levels[_hls.loadLevel].start_seqnum) + "," + (_levels[_hls.loadLevel].end_seqnum) + "],level " + _hls.loadLevel + " m/M PTS:" + fragData.pts_min + "/" + fragData.pts_max); + Log.debug("Loaded " + _fragCurrent.seqnum + " of [" + (fragLevel.start_seqnum) + "," + (fragLevel.end_seqnum) + "],level " + fragLevelIdx + " m/M PTS:" + fragData.pts_min + "/" + fragData.pts_max); } if (fragData.audio_found || fragData.video_found) { - _levels[_hls.loadLevel].updateFragment(_fragCurrent.seqnum, true, fragData.pts_min, fragData.pts_max + fragData.tag_duration); + fragLevel.updateFragment(_fragCurrent.seqnum, true, fragData.pts_min, fragData.pts_max + fragData.tag_duration); // set pts_start here, it might not be updated directly in updateFragment() if this loaded fragment has been removed from a live playlist fragData.pts_start = fragData.pts_min; - _hls.dispatchEvent(new HLSEvent(HLSEvent.PLAYLIST_DURATION_UPDATED, _levels[_hls.loadLevel].duration)); + _hls.dispatchEvent(new HLSEvent(HLSEvent.PLAYLIST_DURATION_UPDATED, fragLevel.duration)); if (fragData.tags.length) { if (fragData.metadata_tag_injected == false) { fragData.tags.unshift(_fragCurrent.getMetadataTag()); diff --git a/src/org/mangui/hls/stream/StreamBuffer.as b/src/org/mangui/hls/stream/StreamBuffer.as index d928ce2e..e288b733 100644 --- a/src/org/mangui/hls/stream/StreamBuffer.as +++ b/src/org/mangui/hls/stream/StreamBuffer.as @@ -59,8 +59,8 @@ package org.mangui.hls.stream { /* are we using alt-audio ? */ private var _useAltAudio : Boolean; /** playlist sliding (non null for live playlist) **/ - private var _playlistSlidingMain : Number; - private var _playlistSlidingAltAudio : Number; + private var _liveSlidingMain : Number; + private var _liveSlidingAltAudio : Number; // these 2 variables are used to compute main and altaudio live playlist sliding private var _nextExpectedAbsoluteStartPosMain : Number; private var _nextExpectedAbsoluteStartPosAltAudio : Number; @@ -140,7 +140,7 @@ package org.mangui.hls.stream { CONFIG::LOGGING { Log.debug("seek in buffer"); // seek position requested is an absolute position, add sliding main to make it absolute - _seekPositionRequested+= _playlistSlidingMain; + _seekPositionRequested+= _liveSlidingMain; } } else { // stop any load in progress ... @@ -170,11 +170,11 @@ package org.mangui.hls.stream { var nextRelativeStartPos: Number = startPosition + (max_pts - min_pts) / 1000; var headerAppended : Boolean = false, metaAppended : Boolean = false; // compute sliding in case of live playlist, or in case of VoD playlist that slided in the past (live sliding ended playlist) - var computeSliding : Boolean = (_hls.type == HLSTypes.LIVE || _playlistSlidingMain || _playlistSlidingAltAudio); + var computeSliding : Boolean = (_hls.type == HLSTypes.LIVE || _liveSlidingMain || _liveSlidingAltAudio); var fragIdx : int; if(fragmentType == HLSLoaderTypes.FRAGMENT_MAIN) { - sliding = _playlistSlidingMain; + sliding = _liveSlidingMain; // if a new fragment is being appended if(fragLevel != _fragMainLevel || fragSN != _fragMainSN) { _fragMainLevel = fragLevel; @@ -186,9 +186,14 @@ package org.mangui.hls.stream { if(_nextExpectedAbsoluteStartPosMain !=-1) { // if same continuity counter, sliding can be computed using PTS, it will be more accurate if(continuity == _fragMainInitialContinuity) { - sliding = _playlistSlidingMain = _fragMainInitialStartPosition + (min_pts-_fragMainInitialPTS)/1000 - startPosition; + sliding = _liveSlidingMain = _fragMainInitialStartPosition + (min_pts-_fragMainInitialPTS)/1000 - startPosition; + CONFIG::LOGGING { + if(sliding < 0) { + Log.warn('negative sliding : sliding/min_pts/_fragMainInitialPTS/startPosition/_fragMainInitialStartPosition:' + sliding + '/' + min_pts + '/' + _fragMainInitialPTS + '/' + startPosition.toFixed(3) + '/' + _fragMainInitialStartPosition); + } + } } else { - sliding = _playlistSlidingMain = _nextExpectedAbsoluteStartPosMain - startPosition; + sliding = _liveSlidingMain = _nextExpectedAbsoluteStartPosMain - startPosition; } } else { _fragMainInitialStartPosition = startPosition; @@ -204,7 +209,7 @@ package org.mangui.hls.stream { } fragIdx = _fragMainIdx; } else { - sliding = _playlistSlidingAltAudio; + sliding = _liveSlidingAltAudio; // if a new fragment is being appended if(fragLevel != _fragAltAudioLevel || fragSN != _fragAltAudioSN) { _fragAltAudioLevel = fragLevel; @@ -216,9 +221,9 @@ package org.mangui.hls.stream { if(_nextExpectedAbsoluteStartPosAltAudio !=-1) { // if same continuity counter, sliding can be computed using PTS, it will be more accurate if(continuity == _fragAltAudioInitialContinuity) { - sliding = _playlistSlidingAltAudio = _fragAltAudioInitialStartPosition + (min_pts - _fragAltAudioInitialPTS)/1000 - startPosition; + sliding = _liveSlidingAltAudio = _fragAltAudioInitialStartPosition + (min_pts - _fragAltAudioInitialPTS)/1000 - startPosition; } else { - sliding = _playlistSlidingAltAudio = _nextExpectedAbsoluteStartPosAltAudio - startPosition; + sliding = _liveSlidingAltAudio = _nextExpectedAbsoluteStartPosAltAudio - startPosition; } } else { _fragAltAudioInitialStartPosition = startPosition; @@ -284,12 +289,12 @@ package org.mangui.hls.stream { switch(_hls.seekState) { case HLSSeekStates.SEEKING: // _seekPositionRequested is an absolute position, convert it to relative by substracting sliding - return _seekPositionRequested - _playlistSlidingMain; + return _seekPositionRequested - _liveSlidingMain; case HLSSeekStates.SEEKED: case HLSSeekStates.IDLE: default: /** Relative playback position = Absolute Position (which is Absolute seek position + NetStream playback time) - playlist sliding **/ - var pos: Number = _seekPositionReal + _hls.stream.time - _playlistSlidingMain; + var pos: Number = _seekPositionReal + _hls.stream.time - _liveSlidingMain; if(isNaN(pos)) { pos = 0; } @@ -297,6 +302,14 @@ package org.mangui.hls.stream { } } + public function get liveSlidingMain() : Number { + return _liveSlidingMain; + } + + public function get liveSlidingAltAudio() : Number { + return _liveSlidingAltAudio; + } + public function get reachedEnd() : Boolean { return _reachedEnd; } @@ -317,7 +330,7 @@ package org.mangui.hls.stream { _fragMainIdx = _fragAltAudioIdx = 0; _seekPositionReached = false; _reachedEnd = false; - _playlistSlidingMain = _playlistSlidingAltAudio = 0; + _liveSlidingMain = _liveSlidingAltAudio = 0; _nextExpectedAbsoluteStartPosMain = _nextExpectedAbsoluteStartPosAltAudio = -1; CONFIG::LOGGING { Log.debug("StreamBuffer flushed"); @@ -330,7 +343,7 @@ package org.mangui.hls.stream { _audioIdx = 0; FLVData.refPTSAltAudio = NaN; _nextExpectedAbsoluteStartPosAltAudio = -1; - _playlistSlidingAltAudio = 0; + _liveSlidingAltAudio = 0; var _filteredHeaderTags : Vector. = _headerTags.filter(filterAACHeader); _headerIdx -= (_headerTags.length - _filteredHeaderTags.length); } @@ -395,9 +408,9 @@ package org.mangui.hls.stream { public function get bufferLength() : Number { switch(_hls.seekState) { case HLSSeekStates.SEEKING: - /* max_pos is a relative max, seekPositionRequested is absolute. we need to add _playlistSlidingMain + /* max_pos is a relative max, seekPositionRequested is absolute. we need to add _liveSlidingMain in order to compare apple to apple */ - return Math.max(0, max_pos + _playlistSlidingMain - _seekPositionRequested); + return Math.max(0, max_pos + _liveSlidingMain - _seekPositionRequested); case HLSSeekStates.SEEKED: if (audioExpected) { if (videoExpected) { @@ -482,7 +495,7 @@ package org.mangui.hls.stream { var duration : Number = _playlistDuration; // dispatch media time event only if position/buffer or playlist duration has changed if(pos != _lastPos || bufLen != _lastBufLen || duration != _lastDuration) { - _hls.dispatchEvent(new HLSEvent(HLSEvent.MEDIA_TIME, new HLSMediatime(pos, duration, bufLen, backBufferLength, _playlistSlidingMain, _playlistSlidingAltAudio))); + _hls.dispatchEvent(new HLSEvent(HLSEvent.MEDIA_TIME, new HLSMediatime(pos, duration, bufLen, backBufferLength, _liveSlidingMain, _liveSlidingAltAudio))); _lastPos = pos; _lastDuration = duration; _lastBufLen = bufLen; @@ -506,9 +519,9 @@ package org.mangui.hls.stream { * check if buffer max absolute position is greater than requested seek position * if it is the case, then we can start injecting tags in NetStream * max_pos is a relative max, here we need to compare against absolute max position, so - * we need to add _playlistSlidingMain to convert from relative to absolute + * we need to add _liveSlidingMain to convert from relative to absolute */ - if ((max_pos+_playlistSlidingMain) >= _seekPositionRequested) { + if ((max_pos+_liveSlidingMain) >= _seekPositionRequested) { // inject enough tags to reach seek position tagDuration = _seekPositionRequested + MAX_NETSTREAM_BUFFER_SIZE - min_min_pos; } @@ -517,7 +530,9 @@ package org.mangui.hls.stream { var data : Vector. = shiftmultipletags(tagDuration); if (!_seekPositionReached) { data = seekFilterTags(data, _seekPositionRequested); - _seekPositionReached = true; + if(data.length) { + _seekPositionReached = true; + } } var tags : Vector. = new Vector.(); @@ -526,8 +541,8 @@ package org.mangui.hls.stream { } if (tags.length) { CONFIG::LOGGING { - var t0 : Number = data[0].positionAbsolute - _playlistSlidingMain; - var t1 : Number = data[data.length - 1].positionAbsolute - _playlistSlidingMain; + var t0 : Number = data[0].positionAbsolute - _liveSlidingMain; + var t1 : Number = data[data.length - 1].positionAbsolute - _liveSlidingMain; Log.debug("appending " + tags.length + " tags, start/end :" + t0.toFixed(2) + "/" + t1.toFixed(2)); } (_hls.stream as HLSNetStream).appendTags(tags); @@ -547,36 +562,50 @@ package org.mangui.hls.stream { var idx2Clone : Vector. = new Vector.(); // loop through all tags and find index position of header tags located before start position - for (var i : int = 0; i < tags.length; i++) { - var data : FLVData = tags[i]; - if (data.positionAbsolute <= absoluteStartPosition) { - lastIdx = i; - // current tag is before requested start position - // grab AVC/AAC/DISCONTINUITY/METADATA/KEYFRAMES tag located just before - switch(data.tag.type) { - case FLVTag.DISCONTINUITY: - disIdx = i; - break; - case FLVTag.METADATA: - if(data.loaderType == HLSLoaderTypes.FRAGMENT_MAIN) { - metIdxMain = i; - } else { - metIdxAltAudio = i; - } - break; - case FLVTag.AAC_HEADER: - aacIdx = i; - break; - case FLVTag.AVC_HEADER: - avcIdx = i; - break; - case FLVTag.AVC_NALU: - if (data.tag.keyframe) keyIdx = i; - default: - break; + while(lastIdx ==-1) { + for (var i : int = 0; i < tags.length; i++) { + var data : FLVData = tags[i]; + if (data.positionAbsolute <= absoluteStartPosition) { + lastIdx = i; + // current tag is before requested start position + // grab AVC/AAC/DISCONTINUITY/METADATA/KEYFRAMES tag located just before + switch(data.tag.type) { + case FLVTag.DISCONTINUITY: + disIdx = i; + break; + case FLVTag.METADATA: + if(data.loaderType == HLSLoaderTypes.FRAGMENT_MAIN) { + metIdxMain = i; + } else { + metIdxAltAudio = i; + } + break; + case FLVTag.AAC_HEADER: + aacIdx = i; + break; + case FLVTag.AVC_HEADER: + avcIdx = i; + break; + case FLVTag.AVC_NALU: + if (data.tag.keyframe) keyIdx = i; + default: + break; + } + } else { + break; + } + } + if(lastIdx == -1) { + // all filtered tags are located after seek position ... tweak start position + CONFIG::LOGGING { + Log.warn("seekFilterTags: startPosition > first tag position:" + absoluteStartPosition.toFixed(3) + '/' + tags[0].positionAbsolute.toFixed(3)); + } + if(absoluteStartPosition != tags[0].positionAbsolute) { + absoluteStartPosition = tags[0].positionAbsolute; + } else { + // nothing found yet, let's return empty an array + return filteredTags; } - } else { - break; } } @@ -650,8 +679,8 @@ package org.mangui.hls.stream { if(videoExpected) { // find last video keyframe before clipping_position : loop through header tags and find last AVC_HEADER before clipping position for each (var data : FLVData in _headerTags) { - if ((data.positionAbsolute - _playlistSlidingMain ) <= clipping_position0 && data.tag.type == FLVTag.AVC_HEADER) { - clipping_position = data.positionAbsolute - _playlistSlidingMain; + if ((data.positionAbsolute - _liveSlidingMain ) <= clipping_position0 && data.tag.type == FLVTag.AVC_HEADER) { + clipping_position = data.positionAbsolute - _liveSlidingMain; } } @@ -675,19 +704,19 @@ package org.mangui.hls.stream { var clipped_tags : uint = 0; // loop through each tag list and clip tags if out of max back buffer boundary - while (_audioTags.length && (_audioTags[0].positionAbsolute - _playlistSlidingMain ) < clipping_position) { + while (_audioTags.length && (_audioTags[0].positionAbsolute - _liveSlidingMain ) < clipping_position) { _audioTags.shift(); _audioIdx--; clipped_tags++; } - while (_videoTags.length && (_videoTags[0].positionAbsolute - _playlistSlidingMain ) < clipping_position) { + while (_videoTags.length && (_videoTags[0].positionAbsolute - _liveSlidingMain ) < clipping_position) { _videoTags.shift(); _videoIdx--; clipped_tags++; } - while (_metaTags.length && (_metaTags[0].positionAbsolute - _playlistSlidingMain ) < clipping_position) { + while (_metaTags.length && (_metaTags[0].positionAbsolute - _liveSlidingMain ) < clipping_position) { _metaTags.shift(); _metaIdx--; clipped_tags++; @@ -707,7 +736,7 @@ package org.mangui.hls.stream { var headercounter : uint = 0; var _newheaderTags : Vector. = new Vector.(); for each (data in _headerTags) { - if ((data.positionAbsolute - _playlistSlidingMain ) < clipping_position) { + if ((data.positionAbsolute - _liveSlidingMain ) < clipping_position) { headercounter++; switch(data.tag.type) { case FLVTag.DISCONTINUITY: @@ -878,13 +907,13 @@ package org.mangui.hls.stream { private function get min_audio_pos() : Number { var min_pos_ : Number = Number.POSITIVE_INFINITY; - if (_audioTags.length) min_pos_ = Math.min(min_pos_, _audioTags[0].positionAbsolute - _playlistSlidingMain ); + if (_audioTags.length) min_pos_ = Math.min(min_pos_, _audioTags[0].positionAbsolute - _liveSlidingMain ); return min_pos_; } private function get min_video_pos() : Number { var min_pos_ : Number = Number.POSITIVE_INFINITY; - if (_videoTags.length) min_pos_ = Math.min(min_pos_, _videoTags[0].positionAbsolute - _playlistSlidingMain ); + if (_videoTags.length) min_pos_ = Math.min(min_pos_, _videoTags[0].positionAbsolute - _liveSlidingMain ); return min_pos_; } @@ -902,13 +931,13 @@ package org.mangui.hls.stream { private function get max_audio_pos() : Number { var max_pos_ : Number = Number.NEGATIVE_INFINITY; - if (_audioTags.length) max_pos_ = Math.max(max_pos_, _audioTags[_audioTags.length - 1].positionAbsolute - _playlistSlidingMain ); + if (_audioTags.length) max_pos_ = Math.max(max_pos_, _audioTags[_audioTags.length - 1].positionAbsolute - _liveSlidingMain ); return max_pos_; } private function get max_video_pos() : Number { var max_pos_ : Number = Number.NEGATIVE_INFINITY; - if (_videoTags.length) max_pos_ = Math.max(max_pos_, _videoTags[_videoTags.length - 1].positionAbsolute - _playlistSlidingMain ); + if (_videoTags.length) max_pos_ = Math.max(max_pos_, _videoTags[_videoTags.length - 1].positionAbsolute - _liveSlidingMain ); return max_pos_; } diff --git a/src/org/mangui/hls/utils/Log.as b/src/org/mangui/hls/utils/Log.as index 3489df50..44934a58 100644 --- a/src/org/mangui/hls/utils/Log.as +++ b/src/org/mangui/hls/utils/Log.as @@ -3,6 +3,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mangui.hls.utils { import flash.external.ExternalInterface; + import flash.utils.ByteArray; + import flash.net.ObjectEncoding; + + import by.blooddy.crypto.Base64; import org.mangui.hls.HLSSettings; @@ -44,5 +48,131 @@ package org.mangui.hls.utils { ExternalInterface.call('console.log', level + message); else trace(level + message); } + + public static function outputCCFLVTagToConsole(prefix:String, data:ByteArray):void + { + data.position = 11; + data.objectEncoding = ObjectEncoding.AMF0; + var method:* = data.readObject(); + if (method === "onCaptionInfo") + { + data.objectEncoding = ObjectEncoding.AMF3; + data.readUnsignedByte(); + var object:* = data.readObject(); + + if (object && object.type && object.data) + { + var ba:ByteArray = Base64.decode(object.data); + ba.readUnsignedInt(); + var total:int = 31 & ba.readUnsignedByte(); + ba.readUnsignedByte(); + Log.outputCCDataToConsole(prefix , ba, total); + } + } + } + + public static function outputCCDataToConsole(prefix:String, data:ByteArray, total:int):void + { + // The following code is for debug logging... + //var byte:uint; + var byte:int; + var ccbyte1:int; + var ccbyte2:int; + var ccValid:Boolean = false; + var ccType:int; + var assembling:Boolean = false; + + var output:String = ""; + for (var i:int=0; i= 32 || ccbyte2 > 32) + { + output += String.fromCharCode(ccbyte1) + " " + String.fromCharCode(ccbyte2); + } + } + else if (ccType == 1) // todo this might be language 2? + { + // + } + // TODO: assemble DTVCC packets. not sure if needed... + else if (ccType == 3) + { + if (assembling) + { + // close previous packet + assembling = false; + } + // Start assembling packet + assembling = true; + } + else if (ccType == 2) + { + if (ccValid == false && assembling) + { + // close previous packet + assembling = false; + } + // append bytes to packet + } + } + + //output += "\n"; + } + + if (output) + { + Log.info(prefix + ": " + output + "\n"); + } + } }; } \ No newline at end of file diff --git a/src/org/mangui/osmf/plugins/traits/HLSPlayTrait.as b/src/org/mangui/osmf/plugins/traits/HLSPlayTrait.as index 964406b1..47c62077 100644 --- a/src/org/mangui/osmf/plugins/traits/HLSPlayTrait.as +++ b/src/org/mangui/osmf/plugins/traits/HLSPlayTrait.as @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mangui.osmf.plugins.traits { import org.mangui.hls.HLS; + import org.mangui.hls.constant.HLSPlayStates; import org.mangui.hls.event.HLSEvent; import org.osmf.traits.PlayState; import org.osmf.traits.PlayTrait; @@ -21,6 +22,7 @@ } super(); _hls = hls; + _hls.addEventListener(HLSEvent.PLAYBACK_STATE, _stateChangedHandler); _hls.addEventListener(HLSEvent.PLAYBACK_COMPLETE, _playbackComplete); } @@ -28,20 +30,22 @@ CONFIG::LOGGING { Log.debug("HLSPlayTrait:dispose"); } + _hls.removeEventListener(HLSEvent.PLAYBACK_STATE, _stateChangedHandler); _hls.removeEventListener(HLSEvent.PLAYBACK_COMPLETE, _playbackComplete); super.dispose(); } - override protected function playStateChangeStart(newPlayState : String) : void { + override protected function playStateChangeStart(newPlayState:String):void { CONFIG::LOGGING { Log.info("HLSPlayTrait:playStateChangeStart:" + newPlayState); } - switch(newPlayState) { + switch (newPlayState) { case PlayState.PLAYING: if (!streamStarted) { _hls.stream.play(); streamStarted = true; - } else { + } + else { _hls.stream.resume(); } break; @@ -55,9 +59,24 @@ } } + /** state changed handler **/ + private function _stateChangedHandler(event:HLSEvent):void { + switch (event.state) { + case HLSPlayStates.PLAYING: + CONFIG::LOGGING { + Log.debug("HLSPlayTrait:_stateChangedHandler:setBuffering(true)"); + } + if (!streamStarted) { + streamStarted = true; + play(); + } + default: + } + } + /** playback complete handler **/ private function _playbackComplete(event : HLSEvent) : void { stop(); } } -} +} \ No newline at end of file