/* 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.controller { // import flash.events.ThrottleEvent; // import flash.events.ThrottleType; import flash.events.Event; import flash.events.TimerEvent; import flash.system.Capabilities; import flash.utils.getTimer; import flash.utils.Timer; import org.mangui.hls.constant.HLSPlayStates; import org.mangui.hls.event.HLSEvent; import org.mangui.hls.HLS; import org.mangui.hls.HLSSettings; CONFIG::LOGGING { import org.mangui.hls.utils.Log; } /* * class that control/monitor FPS */ public class FPSController { /** Reference to the HLS controller. **/ private var _hls : HLS; private var _timer : Timer; private var _throttling : Boolean; private var _playing : Boolean; private var _lastTime : int; private var _lastDroppedFrames : int; // hardcode event name and state to avoid compilation issue with target player < 11.2 private static const THROTTLE : String = "throttle"; private static const RESUME : String = "resume"; public function FPSController(hls : HLS) { _hls = hls; _throttling = false; _playing = false; _lastTime = 0; /** Check that Flash Player version is sufficient (11.2 or above) to use throttling event **/ if(_checkVersion() >= 11.2) { _hls.addEventListener(HLSEvent.PLAYBACK_STATE, _playbackStateHandler); _hls.addEventListener(HLSEvent.STAGE_SET, _stageSetHandler); } } private function _checkVersion() : Number { var verArray : Array = Capabilities.version.split(/\s|,/); return Number(String(verArray[1] + "." + verArray[2])); } public function dispose() : void { if(_checkVersion() >= 11.2) { _hls.removeEventListener(HLSEvent.PLAYBACK_STATE, _playbackStateHandler); _hls.removeEventListener(HLSEvent.STAGE_SET, _stageSetHandler); if(_timer) { _timer.removeEventListener(TimerEvent.TIMER, _checkFPS); _timer.stop(); } if(_hls.stage) { _hls.stage.removeEventListener(THROTTLE, onThrottle); } } } private function _stageSetHandler(event : HLSEvent) : void { CONFIG::LOGGING { Log.debug("FPSController:stage defined, listen to throttle event"); } _timer = new Timer(HLSSettings.fpsDroppedMonitoringPeriod,0); _timer.addEventListener(TimerEvent.TIMER, _checkFPS); _timer.start(); _hls.stage.addEventListener(THROTTLE, onThrottle); } private function _playbackStateHandler(event : HLSEvent) : void { switch(event.state) { case HLSPlayStates.PLAYING: // start fps monitoring when switching to playing state _playing = true; _lastTime = 0; CONFIG::LOGGING { Log.debug("FPSController:playback starting, start monitoring FPS"); } break; default: _playing = false; // stop fps monitoring in all other cases CONFIG::LOGGING { Log.debug("FPSController:playback stopped, stop monitoring FPS"); } break; } }; private function onThrottle(e : Object) : void { CONFIG::LOGGING { Log.debug("FPSController:onThrottle:" + e.state + ',fps:' + e.targetFrameRate); } switch(e.state) { case RESUME: _throttling=false; _lastTime = 0; break; default: _throttling=true; break; } } private function _checkFPS(e : Event) : void { var currentTime : int = getTimer(); var droppedFrames : int = _hls.stream.info.droppedFrames; // monitor only if not throttling AND playing AND we hold a time reference with nb of dropped frames if(_throttling == false && _playing == true && _lastTime) { var currentPeriod : int = currentTime-_lastTime; var currentDropped : int = droppedFrames - _lastDroppedFrames; var currentDropFPS : Number = 1000*currentDropped/currentPeriod; var currentFPS : Number = _hls.stream.currentFPS; CONFIG::LOGGING { Log.debug2("FPSController:currentDropped,currentPeriod,currentDropFPS," + currentDropped +',' + currentPeriod +',' + currentDropFPS.toFixed(1)); } if(currentDropFPS > HLSSettings.fpsDroppedMonitoringThreshold*currentFPS) { CONFIG::LOGGING { Log.warn("FPSController:drop FPS ratio >" + HLSSettings.fpsDroppedMonitoringThreshold + ',drop/displayed:' +currentDropFPS.toFixed(1)+","+currentFPS.toFixed(1) + " in the last " + HLSSettings.fpsDroppedMonitoringPeriod + "ms"); } _hls.dispatchEvent(new HLSEvent(HLSEvent.FPS_DROP, _hls.currentLevel)); if(HLSSettings.capLevelonFPSDrop) { var currentLevel : int = _hls.currentLevel; if(currentLevel > 0 && (_hls.autoLevelCapping == -1 || _hls.autoLevelCapping >= _hls.currentLevel)) { var capLevel : int = currentLevel-1; CONFIG::LOGGING { Log.warn("FPSController:cap level on FPS drop to " + capLevel); } _hls.autoLevelCapping = capLevel; _hls.dispatchEvent(new HLSEvent(HLSEvent.FPS_DROP_LEVEL_CAPPING, capLevel)); if(HLSSettings.smoothAutoSwitchonFPSDrop) { if(_hls.autoLevel == true) { CONFIG::LOGGING { Log.warn("FPSController:trigger smooth level switch on frame drop"); } _hls.nextLevel = -1; _hls.dispatchEvent(new HLSEvent(HLSEvent.FPS_DROP_SMOOTH_LEVEL_SWITCH)); } } } } } } _lastTime = currentTime; _lastDroppedFrames = droppedFrames; } } }