/* 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. = 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(); } }; } }