169 lines
7.5 KiB
ActionScript
169 lines
7.5 KiB
ActionScript
/* 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.loader {
|
|
import flash.events.ErrorEvent;
|
|
import flash.events.IOErrorEvent;
|
|
import flash.events.SecurityErrorEvent;
|
|
import flash.utils.clearTimeout;
|
|
import flash.utils.getTimer;
|
|
import flash.utils.setTimeout;
|
|
import org.mangui.hls.constant.HLSPlayStates;
|
|
import org.mangui.hls.event.HLSError;
|
|
import org.mangui.hls.event.HLSEvent;
|
|
import org.mangui.hls.event.HLSLoadMetrics;
|
|
import org.mangui.hls.HLS;
|
|
import org.mangui.hls.HLSSettings;
|
|
import org.mangui.hls.model.AudioTrack;
|
|
import org.mangui.hls.model.Fragment;
|
|
import org.mangui.hls.model.Level;
|
|
import org.mangui.hls.playlist.AltAudioTrack;
|
|
import org.mangui.hls.playlist.Manifest;
|
|
|
|
CONFIG::LOGGING {
|
|
import org.mangui.hls.utils.Log;
|
|
}
|
|
public class AltAudioLevelLoader {
|
|
/** Reference to the hls framework controller. **/
|
|
private var _hls : HLS;
|
|
/** Link to the M3U8 file. **/
|
|
private var _url : String;
|
|
/** Timeout ID for reloading live playlists. **/
|
|
private var _timeoutID : uint;
|
|
/** last reload manifest time **/
|
|
private var _reloadPlaylistTimer : uint;
|
|
/** current audio level **/
|
|
private var _currentTrack : int;
|
|
/** reference to manifest being loaded **/
|
|
private var _manifestLoading : Manifest;
|
|
/** is this loader closed **/
|
|
private var _closed : Boolean = false;
|
|
/* playlist retry timeout */
|
|
private var _retryTimeout : Number;
|
|
private var _retryCount : int;
|
|
|
|
/** Setup the loader. **/
|
|
public function AltAudioLevelLoader(hls : HLS) {
|
|
_hls = hls;
|
|
_hls.addEventListener(HLSEvent.PLAYBACK_STATE, _stateHandler);
|
|
_hls.addEventListener(HLSEvent.AUDIO_TRACK_SWITCH, _audioTrackSwitchHandler);
|
|
};
|
|
|
|
public function dispose() : void {
|
|
_close();
|
|
_hls.removeEventListener(HLSEvent.PLAYBACK_STATE, _stateHandler);
|
|
_hls.removeEventListener(HLSEvent.AUDIO_TRACK_SWITCH, _audioTrackSwitchHandler);
|
|
}
|
|
|
|
/** Loading failed; return errors. **/
|
|
private function _errorHandler(event : ErrorEvent) : void {
|
|
var txt : String;
|
|
var code : int;
|
|
if (event is SecurityErrorEvent) {
|
|
code = HLSError.MANIFEST_LOADING_CROSSDOMAIN_ERROR;
|
|
txt = "Cannot load M3U8: crossdomain access denied:" + event.text;
|
|
} else if (event is IOErrorEvent && (HLSSettings.manifestLoadMaxRetry == -1 || _retryCount < HLSSettings.manifestLoadMaxRetry)) {
|
|
CONFIG::LOGGING {
|
|
Log.warn("I/O Error while trying to load Playlist, retry in " + _retryTimeout + " ms");
|
|
}
|
|
_timeoutID = setTimeout(_loadAudioLevelPlaylist, _retryTimeout);
|
|
/* exponential increase of retry timeout, capped to manifestLoadMaxRetryTimeout */
|
|
_retryTimeout = Math.min(HLSSettings.manifestLoadMaxRetryTimeout, 2 * _retryTimeout);
|
|
_retryCount++;
|
|
return;
|
|
} else {
|
|
code = HLSError.MANIFEST_LOADING_IO_ERROR;
|
|
txt = "Cannot load M3U8: " + event.text;
|
|
}
|
|
var hlsError : HLSError = new HLSError(code, _url, txt);
|
|
_hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, hlsError));
|
|
};
|
|
|
|
/** parse a playlist **/
|
|
private function _parseAudioPlaylist(string : String, url : String, level : int, metrics : HLSLoadMetrics) : void {
|
|
if (string != null && string.length != 0) {
|
|
CONFIG::LOGGING {
|
|
Log.debug("audio level " + level + " playlist:\n" + string);
|
|
}
|
|
var frags : Vector.<Fragment> = Manifest.getFragments(string, url, level);
|
|
// set fragment and update sequence number range
|
|
var audioTrack : AudioTrack = _hls.audioTracks[_currentTrack];
|
|
var audioLevel : Level = audioTrack.level;
|
|
if(audioLevel == null) {
|
|
audioLevel = audioTrack.level = new Level();
|
|
}
|
|
audioLevel.updateFragments(frags);
|
|
audioLevel.targetduration = Manifest.getTargetDuration(string);
|
|
// if stream is live, arm a timer to periodically reload playlist
|
|
if (!Manifest.hasEndlist(string)) {
|
|
var timeout : int = Math.max(100, _reloadPlaylistTimer + 1000 * audioLevel.averageduration - getTimer());
|
|
CONFIG::LOGGING {
|
|
Log.debug("Alt Audio Level Live Playlist parsing finished: reload in " + timeout + " ms");
|
|
}
|
|
_timeoutID = setTimeout(_loadAudioLevelPlaylist, timeout);
|
|
}
|
|
}
|
|
metrics.id = audioLevel.start_seqnum;
|
|
metrics.id2 = audioLevel.end_seqnum;
|
|
_hls.dispatchEvent(new HLSEvent(HLSEvent.AUDIO_LEVEL_LOADED, metrics));
|
|
_manifestLoading = null;
|
|
};
|
|
|
|
/** load/reload active M3U8 playlist **/
|
|
private function _loadAudioLevelPlaylist() : void {
|
|
if (_closed) {
|
|
return;
|
|
}
|
|
_reloadPlaylistTimer = getTimer();
|
|
var altAudioTrack : AltAudioTrack = _hls.altAudioTracks[_hls.audioTracks[_currentTrack].id];
|
|
_manifestLoading = new Manifest();
|
|
_manifestLoading.loadPlaylist(_hls,altAudioTrack.url, _parseAudioPlaylist, _errorHandler, _currentTrack, _hls.type, HLSSettings.flushLiveURLCache);
|
|
_hls.dispatchEvent(new HLSEvent(HLSEvent.AUDIO_LEVEL_LOADING, _currentTrack));
|
|
};
|
|
|
|
/** When audio track switch occurs, assess the need of loading audio level playlist **/
|
|
private function _audioTrackSwitchHandler(event : HLSEvent) : void {
|
|
_currentTrack = event.audioTrack;
|
|
var audioTrack : AudioTrack = _hls.audioTracks[_currentTrack];
|
|
if (audioTrack.source == AudioTrack.FROM_PLAYLIST) {
|
|
var altAudioTrack : AltAudioTrack = _hls.altAudioTracks[audioTrack.id];
|
|
if (altAudioTrack.url && audioTrack.level == null) {
|
|
CONFIG::LOGGING {
|
|
Log.debug("switch to audio track " + _currentTrack + ", load Playlist");
|
|
}
|
|
_retryTimeout = 1000;
|
|
_retryCount = 0;
|
|
_closed = false;
|
|
if(_manifestLoading) {
|
|
_manifestLoading.close();
|
|
_manifestLoading = null;
|
|
}
|
|
clearTimeout(_timeoutID);
|
|
_timeoutID = setTimeout(_loadAudioLevelPlaylist, 0);
|
|
}
|
|
}
|
|
};
|
|
|
|
private function _close() : void {
|
|
CONFIG::LOGGING {
|
|
Log.debug("cancel any audio level load in progress");
|
|
}
|
|
_closed = true;
|
|
clearTimeout(_timeoutID);
|
|
try {
|
|
if (_manifestLoading) {
|
|
_manifestLoading.close();
|
|
}
|
|
} catch(e : Error) {
|
|
}
|
|
}
|
|
|
|
/** When the framework idles out, stop reloading manifest **/
|
|
private function _stateHandler(event : HLSEvent) : void {
|
|
if (event.state == HLSPlayStates.IDLE) {
|
|
_close();
|
|
}
|
|
};
|
|
}
|
|
}
|