/** * @author Jefferson González * @copyright 2010 Jefferson González * * @license * This file is part of Jaris FLV Player. * * Jaris FLV Player is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License or GNU LESSER GENERAL * PUBLIC LICENSE as published by the Free Software Foundation, either version * 3 of the License, or (at your option) any later version. * * Jaris FLV Player is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License and * GNU LESSER GENERAL PUBLIC LICENSE along with Jaris FLV Player. If not, * see . */ package jaris.player; import flash.display.Loader; import flash.display.MovieClip; import flash.display.Sprite; import flash.display.Stage; import flash.display.StageDisplayState; import flash.events.AsyncErrorEvent; import flash.events.Event; import flash.events.EventDispatcher; import flash.events.FullScreenEvent; import flash.events.IOErrorEvent; import flash.events.KeyboardEvent; import flash.events.MouseEvent; import flash.events.NetStatusEvent; import flash.events.ProgressEvent; import flash.events.TimerEvent; import flash.geom.Rectangle; import flash.Lib; import flash.media.ID3Info; import flash.media.Sound; import flash.media.SoundChannel; import flash.media.SoundTransform; import flash.media.Video; import flash.net.NetConnection; import flash.net.NetStream; import flash.net.URLRequest; import flash.system.Capabilities; import flash.system.Security; import flash.ui.Keyboard; import flash.ui.Mouse; import flash.utils.Timer; import jaris.events.PlayerEvents; import jaris.utils.Utils; import jaris.player.AspectRatio; import jaris.player.UserSettings; /** * Jaris main video player */ class Player extends EventDispatcher { //{Member variables private var _stage:Stage; private var _movieClip:MovieClip; private var _connection:NetConnection; private var _stream:NetStream; private var _fullscreen:Bool; private var _soundMuted:Bool; private var _volume:Float; private var _bufferTime:Float; private var _mouseVisible:Bool; private var _mediaLoaded:Bool; private var _hideMouseTimer:Timer; private var _checkAudioTimer:Timer; private var _mediaSource:String; private var _type:String; private var _streamType:String; private var _server:String; //For future use on rtmp private var _sound:Sound; private var _soundChannel:SoundChannel; private var _id3Info:ID3Info; private var _video:Video; private var _videoWidth:Float; private var _videoHeight:Float; private var _videoMask:Sprite; private var _videoQualityHigh:Bool; private var _mediaDuration:Float; private var _lastTime:Float; private var _lastProgress:Float; private var _isPlaying:Bool; private var _aspectRatio:Float; private var _currentAspectRatio:String; private var _originalAspectRatio:Float; private var _mediaEndReached:Bool; private var _seekPoints:Array; private var _downloadCompleted:Bool; private var _startTime:Float; private var _firstLoad:Bool; private var _stopped:Bool; private var _useHardWareScaling:Bool; private var _youtubeLoader:Loader; private var _userSettings:UserSettings; //} //{Constructor public function new() { super(); //{Main Variables Init _stage = Lib.current.stage; _movieClip = Lib.current; _mouseVisible = true; _soundMuted = false; _volume = 1.0; _bufferTime = 10; _fullscreen = false; _mediaLoaded = false; _hideMouseTimer = new Timer(1500); _checkAudioTimer = new Timer(100); _seekPoints = new Array(); _downloadCompleted = false; _startTime = 0; _firstLoad = true; _stopped = false; _videoQualityHigh = false; _isPlaying = false; _streamType = StreamType.FILE; _type = InputType.VIDEO; _server = ""; _currentAspectRatio = "original"; _aspectRatio = 0; _lastTime = 0; _lastProgress = 0; _userSettings = new UserSettings(); //} //{Initialize sound object _sound = new Sound(); _sound.addEventListener(Event.COMPLETE, onSoundComplete); _sound.addEventListener(Event.ID3, onSoundID3); _sound.addEventListener(IOErrorEvent.IO_ERROR, onSoundIOError); _sound.addEventListener(ProgressEvent.PROGRESS, onSoundProgress); //} //{Initialize video and connection objects _connection = new NetConnection(); _connection.client = this; _connection.connect(null); _stream = new NetStream(_connection); _video = new Video(_stage.stageWidth, _stage.stageHeight); _movieClip.addChild(_video); //} //Video mask so that custom menu items work _videoMask = new Sprite(); _movieClip.addChild(_videoMask); //Set initial rendering to high quality toggleQuality(); //{Initialize system event listeners _movieClip.addEventListener(Event.ENTER_FRAME, onEnterFrame); _stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); _stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove); _stage.addEventListener(FullScreenEvent.FULL_SCREEN, onFullScreen); _stage.addEventListener(Event.RESIZE, onResize); _hideMouseTimer.addEventListener(TimerEvent.TIMER, hideMouseTimer); _checkAudioTimer.addEventListener(TimerEvent.TIMER, checkAudioTimer); _connection.addEventListener(NetStatusEvent.NET_STATUS, onNetStatus); _connection.addEventListener(AsyncErrorEvent.ASYNC_ERROR, onAsyncError); //} } //} //{Timers /** * Timer that hides the mouse pointer when it is idle and dispatch the PlayerEvents.MOUSE_HIDE * @param event */ private function hideMouseTimer(event:TimerEvent):Void { if (_fullscreen) { if (_mouseVisible) { _mouseVisible = false; } else { Mouse.hide(); callEvents(PlayerEvents.MOUSE_HIDE); _hideMouseTimer.stop(); } } } /** * To check if the sound finished playing * @param event */ private function checkAudioTimer(event:TimerEvent):Void { if (_soundChannel.position + 100 >= _sound.length) { _isPlaying = false; _mediaEndReached = true; callEvents(PlayerEvents.PLAYBACK_FINISHED); _checkAudioTimer.stop(); } } //} //{Events /** * Callback after bandwidth calculation for rtmp streams */ private function onBWDone():Void { //Need to study this more } /** * Triggers error event on rtmp connections * @param event */ private function onAsyncError(event:AsyncErrorEvent):Void { //TODO: Should trigger event for controls to display error message trace(event.error); } /** * Checks if connection failed or succeed * @param event */ private function onNetStatus(event:NetStatusEvent):Void { switch (event.info.code) { case "NetConnection.Connect.Success": if (_streamType == StreamType.RTMP) { _stream = new NetStream(_connection); _stream.addEventListener(NetStatusEvent.NET_STATUS, onNetStatus); _stream.bufferTime = 10; _stream.play(Utils.rtmpSourceParser(_mediaSource), true); _stream.client = this; if(_type == InputType.VIDEO) {_video.attachNetStream(_stream); } } callEvents(PlayerEvents.CONNECTION_SUCCESS); case "NetStream.Play.StreamNotFound": trace("Stream not found: " + _mediaSource); //Replace with a dispatch for error event callEvents(PlayerEvents.CONNECTION_FAILED); case "NetStream.Play.Stop": if (_streamType != StreamType.RTMP) { if (_isPlaying) { _stream.togglePause(); } _isPlaying = false; _mediaEndReached = true; callEvents(PlayerEvents.PLAYBACK_FINISHED); } case "NetStream.Play.Start": _isPlaying = true; _mediaEndReached = false; if (_stream.bytesLoaded != _stream.bytesTotal || _streamType == StreamType.RTMP) { callEvents(PlayerEvents.BUFFERING); } case "NetStream.Seek.Notify": _mediaEndReached = false; if (_streamType == StreamType.RTMP) { _isPlaying = true; callEvents(PlayerEvents.PLAY_PAUSE); callEvents(PlayerEvents.BUFFERING); } case "NetStream.Buffer.Empty": if (_stream.bytesLoaded != _stream.bytesTotal) { callEvents(PlayerEvents.BUFFERING); } case "NetStream.Buffer.Full": callEvents(PlayerEvents.NOT_BUFFERING); case "NetStream.Buffer.Flush": if (_stream.bytesLoaded == _stream.bytesTotal) { _downloadCompleted = true; } } } /** * Proccess keyboard shortcuts * @param event */ private function onKeyDown(event:KeyboardEvent):Void { var F_KEY:UInt = 70; var M_KEY:UInt = 77; var X_KEY:UInt = 88; switch(event.keyCode) { case Keyboard.TAB: toggleAspectRatio(); case F_KEY: toggleFullscreen(); case M_KEY: toggleMute(); case Keyboard.UP: volumeUp(); case Keyboard.DOWN: volumeDown(); case Keyboard.RIGHT: forward(); case Keyboard.LEFT: rewind(); case Keyboard.SPACE: togglePlay(); case X_KEY: stopAndClose(); } } /** * IF player is full screen shows the mouse when gets hide * @param event */ private function onMouseMove(event:MouseEvent):Void { if (_fullscreen && !_mouseVisible) { if (!_hideMouseTimer.running) { _hideMouseTimer.start(); } _mouseVisible = true; Mouse.show(); callEvents(PlayerEvents.MOUSE_SHOW); } } /** * Resize video player * @param event */ private function onResize(event:Event):Void { resizeAndCenterPlayer(); } /** * Dispath a full screen event to listeners as redraw player an takes care of some other aspects * @param event */ private function onFullScreen(event:FullScreenEvent):Void { _fullscreen = event.fullScreen; if (!event.fullScreen) { Mouse.show(); callEvents(PlayerEvents.MOUSE_SHOW); _mouseVisible = true; } else { _mouseVisible = true; _hideMouseTimer.start(); } resizeAndCenterPlayer(); callEvents(PlayerEvents.FULLSCREEN); } /** * Sits for any cue points available * @param data * @note Planned future implementation */ private function onCuePoint(data:Dynamic):Void { } /** * After a video is loaded this callback gets the video information at start and stores it on variables * @param data */ private function onMetaData(data:Dynamic):Void { if (_firstLoad) { _isPlaying = true; _firstLoad = false; if (data.width) { _videoWidth = data.width; _videoHeight = data.height; } else { _videoWidth = _video.width; _videoHeight = _video.height; } //Store seekpoints times if (data.hasOwnProperty("seekpoints")) //MP4 { for (position in Reflect.fields(data.seekpoints)) { _seekPoints.push(Reflect.field(data.seekpoints, position).time); } } else if (data.hasOwnProperty("keyframes")) //FLV { for (position in Reflect.fields(data.keyframes.times)) { _seekPoints.push(Reflect.field(data.keyframes.times, position)); } } _mediaLoaded = true; _mediaDuration = data.duration; _originalAspectRatio = AspectRatio.getAspectRatio(_videoWidth, _videoHeight); if (_aspectRatio <= 0) { _aspectRatio = _originalAspectRatio; } callEvents(PlayerEvents.MEDIA_INITIALIZED); resizeAndCenterPlayer(); //Retrieve the volume that user selected last time setVolume(_userSettings.getVolume()); } } /** * Dummy function invoked for pseudostream servers * @param data */ private function onLastSecond(data:Dynamic):Void { trace("last second pseudostream"); } /** * Broadcast Timeupdate and Duration */ private function onEnterFrame(event:Event):Void { if (getDuration() > 0 && _lastTime != getCurrentTime()) { _lastTime = getCurrentTime(); callEvents(PlayerEvents.TIME); } if (getBytesLoaded() > 0 && _lastProgress < getBytesLoaded()) { _lastProgress = getBytesLoaded(); callEvents(PlayerEvents.PROGRESS); } } /** * Triggers when playbacks end on rtmp streaming server */ private function onPlayStatus(info:Dynamic):Void { _isPlaying = false; _mediaEndReached = true; callEvents(PlayerEvents.PLAYBACK_FINISHED); } /** * When sound finished downloading * @param event */ private function onSoundComplete(event:Event) { _mediaDuration = _sound.length / 1000; _downloadCompleted = true; callEvents(PlayerEvents.MEDIA_INITIALIZED); } /** * Mimic stream onMetaData * @param event */ private function onSoundID3(event:Event) { if (_firstLoad) { _soundChannel = _sound.play(); _checkAudioTimer.start(); _isPlaying = true; _firstLoad = false; _mediaLoaded = true; _mediaDuration = ((_sound.bytesTotal / _sound.bytesLoaded) * _sound.length) / 1000; _aspectRatio = AspectRatio.getAspectRatio(_videoWidth, _videoHeight); _originalAspectRatio = _aspectRatio; _id3Info = _sound.id3; callEvents(PlayerEvents.CONNECTION_SUCCESS); callEvents(PlayerEvents.MEDIA_INITIALIZED); resizeAndCenterPlayer(); //Retrieve the volume that user selected last time setVolume(_userSettings.getVolume()); } } /** * Dispatch connection failed event on error * @param event */ private function onSoundIOError(event:IOErrorEvent) { callEvents(PlayerEvents.CONNECTION_FAILED); } /** * Monitor sound download progress * @param event */ private function onSoundProgress(event:ProgressEvent) { if (_sound.isBuffering) { callEvents(PlayerEvents.BUFFERING); } else { callEvents(PlayerEvents.NOT_BUFFERING); } _mediaDuration = ((_sound.bytesTotal / _sound.bytesLoaded) * _sound.length) / 1000; callEvents(PlayerEvents.MEDIA_INITIALIZED); } /** * Initializes the youtube loader object * @param event */ private function onYouTubeLoaderInit(event:Event):Void { _youtubeLoader.content.addEventListener("onReady", onYoutubeReady); _youtubeLoader.content.addEventListener("onError", onYoutubeError); _youtubeLoader.content.addEventListener("onStateChange", onYoutubeStateChange); _youtubeLoader.content.addEventListener("onPlaybackQualityChange", onYoutubePlaybackQualityChange); } /** * This event is fired when the player is loaded and initialized, meaning it is ready to receive API calls. */ private function onYoutubeReady(event:Event):Void { _movieClip.addChild(_youtubeLoader.content); _movieClip.setChildIndex(_youtubeLoader.content, 0); Reflect.field(_youtubeLoader.content, "setSize")(_stage.stageWidth, _stage.stageHeight); Reflect.field(_youtubeLoader.content, "loadVideoByUrl")(Utils.youtubeSourceParse(_mediaSource)); callEvents(PlayerEvents.BUFFERING); } /** * This event is fired whenever the player's state changes. Possible values are unstarted (-1), ended (0), * playing (1), paused (2), buffering (3), video cued (5). When the SWF is first loaded it will broadcast * an unstarted (-1) event. When the video is cued and ready to play it will broadcast a video cued event (5). * @param event */ private function onYoutubeStateChange(event:Event):Void { var status:UInt = Std.parseInt(Reflect.field(event, "data")); switch(status) { case -1: callEvents(PlayerEvents.BUFFERING); case 0: _isPlaying = false; _mediaEndReached = true; callEvents(PlayerEvents.PLAYBACK_FINISHED); case 1: if (_firstLoad) { _isPlaying = true; _videoWidth = _stage.stageWidth; _videoHeight = _stage.stageHeight; _firstLoad = false; _mediaLoaded = true; _mediaDuration = Reflect.field(_youtubeLoader.content, "getDuration")(); _aspectRatio = AspectRatio.getAspectRatio(_videoWidth, _videoHeight); _originalAspectRatio = _aspectRatio; callEvents(PlayerEvents.CONNECTION_SUCCESS); callEvents(PlayerEvents.MEDIA_INITIALIZED); resizeAndCenterPlayer(); //Retrieve the volume that user selected last time setVolume(_userSettings.getVolume()); } callEvents(PlayerEvents.NOT_BUFFERING); case 2: callEvents(PlayerEvents.NOT_BUFFERING); case 3: callEvents(PlayerEvents.BUFFERING); case 5: callEvents(PlayerEvents.NOT_BUFFERING); } } /** * This event is fired whenever the video playback quality changes. For example, if you call the * setPlaybackQuality(suggestedQuality) function, this event will fire if the playback quality actually * changes. Your code should respond to the event and should not assume that the quality will automatically * change when the setPlaybackQuality(suggestedQuality) function is called. Similarly, your code should not * assume that playback quality will only change as a result of an explicit call to setPlaybackQuality or any * other function that allows you to set a suggested playback quality. * * The value that the event broadcasts is the new playback quality. Possible values are "small", "medium", * "large" and "hd720". * @param event */ private function onYoutubePlaybackQualityChange(event:Event):Void { //trace(Reflect.field(event, "data")); } /** * This event is fired when an error in the player occurs. The possible error codes are 100, 101, * and 150. The 100 error code is broadcast when the video requested is not found. This occurs when * a video has been removed (for any reason), or it has been marked as private. The 101 error code is * broadcast when the video requested does not allow playback in the embedded players. The error code * 150 is the same as 101, it's just 101 in disguise! * @param event */ private function onYoutubeError(event:Event):Void { trace(Reflect.field(event, "data")); } //} //{Private Methods /** * Function used each time is needed to dispatch an event * @param type */ private function callEvents(type:String):Void { var playerEvent:PlayerEvents = new PlayerEvents(type, true); playerEvent.aspectRatio = getAspectRatio(); playerEvent.duration = getDuration(); playerEvent.fullscreen = isFullscreen(); playerEvent.mute = getMute(); playerEvent.volume = getVolume(); playerEvent.width = _video.width; playerEvent.height = _video.height; playerEvent.stream = getNetStream(); playerEvent.sound = getSound(); playerEvent.time = getCurrentTime(); playerEvent.id3Info = getId3Info(); dispatchEvent(playerEvent); } /** * Reposition and resizes the video player to fit on screen */ private function resizeAndCenterPlayer():Void { if (_streamType != StreamType.YOUTUBE) { _video.height = _stage.stageHeight; _video.width = _video.height * _aspectRatio; _video.x = (_stage.stageWidth / 2) - (_video.width / 2); _video.y = 0; if (_video.width > _stage.stageWidth && _aspectRatio == _originalAspectRatio) { var aspectRatio:Float = _videoHeight / _videoWidth; _video.width = _stage.stageWidth; _video.height = aspectRatio * _video.width; _video.x = 0; _video.y = (_stage.stageHeight / 2) - (_video.height / 2); } _videoMask.graphics.clear(); _videoMask.graphics.lineStyle(); _videoMask.graphics.beginFill(0x000000, 0); _videoMask.graphics.drawRect(_video.x, _video.y, _video.width, _video.height); _videoMask.graphics.endFill(); } else { Reflect.field(_youtubeLoader.content, "setSize")(_stage.stageWidth, _stage.stageHeight); _videoMask.graphics.clear(); _videoMask.graphics.lineStyle(); _videoMask.graphics.beginFill(0x000000, 0); _videoMask.graphics.drawRect(0, 0, _stage.stageWidth, _stage.stageHeight); _videoMask.graphics.endFill(); } callEvents(PlayerEvents.RESIZE); } /** * Check the best seek point available if the seekpoints array is available * @param time time in seconds * @return best seek point in seconds or given one if no seekpoints array is available */ private function getBestSeekPoint(time:Float):Float { if (_seekPoints.length > 0) { var timeOne:String="0"; var timeTwo:String="0"; for(prop in Reflect.fields(_seekPoints)) { if(Reflect.field(_seekPoints,prop) < time) { timeOne = prop; } else { timeTwo = prop; break; } } if(time - _seekPoints[Std.parseInt(timeOne)] < _seekPoints[Std.parseInt(timeTwo)] - time) { return _seekPoints[Std.parseInt(timeOne)]; } else { return _seekPoints[Std.parseInt(timeTwo)]; } } return time; } /** * Checks if the given seek time is already buffered * @param time time in seconds * @return true if can seek false if not in buffer */ private function canSeek(time:Float):Bool { if (_type == InputType.VIDEO) { time = getBestSeekPoint(time); } var cacheTotal = Math.floor((getDuration() - _startTime) * (getBytesLoaded() / getBytesTotal())) - 1; if(time >= _startTime && time < _startTime + cacheTotal) { return true; } return false; } //} //{Public methods /** * Loads a video and starts playing it * @param video video url to load */ public function load(source:String, type:String="video", streamType:String="file", server:String=""):Void { stopAndClose(); _type = type; _streamType = streamType; _mediaSource = source; _stopped = false; _mediaLoaded = false; _firstLoad = true; _startTime = 0; _downloadCompleted = false; _seekPoints = new Array(); _server = server; callEvents(PlayerEvents.BUFFERING); if (_streamType == StreamType.YOUTUBE) { Security.allowDomain("*"); Security.allowDomain("www.youtube.com"); Security.allowDomain("youtube.com"); Security.allowDomain("s.ytimg.com"); Security.allowDomain("i.ytimg.com"); _youtubeLoader = new Loader(); _youtubeLoader.contentLoaderInfo.addEventListener(Event.INIT, onYouTubeLoaderInit); _youtubeLoader.load(new URLRequest("http://www.youtube.com/apiplayer?version=3")); } else if (_type == InputType.VIDEO && (_streamType == StreamType.FILE || _streamType == StreamType.PSEUDOSTREAM)) { _connection.connect(null); _stream = new NetStream(_connection); _stream.addEventListener(NetStatusEvent.NET_STATUS, onNetStatus); _stream.bufferTime = _bufferTime; _stream.play(source); _stream.client = this; _video.attachNetStream(_stream); } else if (_type == InputType.VIDEO && _streamType == StreamType.RTMP) { _connection.connect(_server); } else if (_type == InputType.AUDIO && _streamType == StreamType.RTMP) { _connection.connect(_server); } else if(_type == InputType.AUDIO && _streamType == StreamType.FILE) { _sound.load(new URLRequest(source)); } } /** * Closes the connection and makes player available for another video */ public function stopAndClose():Void { if (_mediaLoaded) { _mediaLoaded = false; _isPlaying = false; _stopped = true; _startTime = 0; if (_streamType == StreamType.YOUTUBE) { Reflect.field(_youtubeLoader.content, "destroy")(); } else if (_type == InputType.VIDEO) { _stream.close(); } else { _soundChannel.stop(); _sound.close(); } } callEvents(PlayerEvents.STOP_CLOSE); } /** * Seeks 8 seconds forward from the current position. * @return current play time after forward */ public function forward():Float { var seekTime = (getCurrentTime() + 8) + _startTime; if (getDuration() > seekTime) { seekTime = seek(seekTime); } return seekTime; } /** * Seeks 8 seconds back from the current position. * @return current play time after rewind */ public function rewind():Float { var seekTime = (getCurrentTime() - 8) + _startTime; if (seekTime >= _startTime) { seekTime = seek(seekTime); } return seekTime; } /** * Seeks video player to a given time in seconds * @param seekTime time in seconds to seek * @return current play time after seeking */ public function seek(seekTime:Float):Float { if (_startTime <= 1 && _downloadCompleted) { if (_type == InputType.VIDEO || _streamType == StreamType.RTMP) { _stream.seek(seekTime); } else if (_type == InputType.AUDIO) { _soundChannel.stop(); _soundChannel = _sound.play(seekTime * 1000); if (!_isPlaying) { _soundChannel.stop(); } setVolume(_userSettings.getVolume()); } } else if(_seekPoints.length > 0 && _streamType == StreamType.PSEUDOSTREAM) { seekTime = getBestSeekPoint(seekTime); if (canSeek(seekTime)) { _stream.seek(seekTime - _startTime); } else if(seekTime != _startTime) { _startTime = seekTime; var url:String; if (_mediaSource.indexOf("?") != -1) { url = _mediaSource + "&start=" + seekTime; } else { url = _mediaSource + "?start=" + seekTime; } _stream.play(url); } } else if (_streamType == StreamType.YOUTUBE) { if (!canSeek(seekTime)) { _startTime = seekTime; Reflect.field(_youtubeLoader.content, "seekTo")(seekTime); } else { Reflect.field(_youtubeLoader.content, "seekTo")(seekTime); } } else if (_streamType == StreamType.RTMP) { // seekTime = getBestSeekPoint(seekTime); //Not Needed? _stream.seek(seekTime); } else if(canSeek(seekTime)) { if (_type == InputType.VIDEO || _streamType == StreamType.RTMP) { _stream.seek(seekTime); } else if (_type == InputType.AUDIO) { _soundChannel.stop(); _soundChannel = _sound.play(seekTime * 1000); if (!_isPlaying) { _soundChannel.stop(); } setVolume(_userSettings.getVolume()); } } callEvents(PlayerEvents.SEEK); return seekTime; } /** * To check wheter the media is playing * @return true if is playing false otherwise */ public function isPlaying():Bool { return _isPlaying; } /** * Cycle betewen aspect ratios * @return new aspect ratio in use */ public function toggleAspectRatio():Float { switch(_currentAspectRatio) { case "original": _aspectRatio = AspectRatio._1_1; _currentAspectRatio = "1:1"; case "1:1": _aspectRatio = AspectRatio._3_2; _currentAspectRatio = "3:2"; case "3:2": _aspectRatio = AspectRatio._4_3; _currentAspectRatio = "4:3"; case "4:3": _aspectRatio = AspectRatio._5_4; _currentAspectRatio = "5:4"; case "5:4": _aspectRatio = AspectRatio._14_9; _currentAspectRatio = "14:9"; case "14:9": _aspectRatio = AspectRatio._14_10; _currentAspectRatio = "14:10"; case "14:10": _aspectRatio = AspectRatio._16_9; _currentAspectRatio = "16:9"; case "16:9": _aspectRatio = AspectRatio._16_10; _currentAspectRatio = "16:10"; case "16:10": _aspectRatio = _originalAspectRatio; _currentAspectRatio = "original"; default: _aspectRatio = _originalAspectRatio; _currentAspectRatio = "original"; } resizeAndCenterPlayer(); callEvents(PlayerEvents.ASPECT_RATIO); //Store aspect ratio into user settings if (_aspectRatio == _originalAspectRatio) { _userSettings.setAspectRatio(0.0); } else { _userSettings.setAspectRatio(_aspectRatio); } return _aspectRatio; } /** * Swithces between play and pause */ public function togglePlay():Bool { if (_mediaLoaded) { if (_mediaEndReached) { _mediaEndReached = false; if (_streamType == StreamType.YOUTUBE) { Reflect.field(_youtubeLoader.content, "seekTo")(0); Reflect.field(_youtubeLoader.content, "playVideo")(); } else if (_type == InputType.VIDEO || _streamType == StreamType.RTMP) { _stream.seek(0); _stream.togglePause(); } else if (_type == InputType.AUDIO) { _checkAudioTimer.start(); _soundChannel = _sound.play(); setVolume(_userSettings.getVolume()); } } else if (_mediaLoaded) { if (_streamType == StreamType.YOUTUBE) { if (_isPlaying) { Reflect.field(_youtubeLoader.content, "pauseVideo")(); } else { Reflect.field(_youtubeLoader.content, "playVideo")(); } } else if (_type == InputType.VIDEO || _streamType == StreamType.RTMP) { _stream.togglePause(); } else if (_type == InputType.AUDIO) { if (_isPlaying) { _soundChannel.stop(); } else { //If end of audio reached start from beggining if (_soundChannel.position + 100 >= _sound.length) { _soundChannel = _sound.play(); } else { _soundChannel = _sound.play(_soundChannel.position); } setVolume(_userSettings.getVolume()); } } } else if (_stopped) { load(_mediaSource, _type, _streamType, _server); } _isPlaying = !_isPlaying; callEvents(PlayerEvents.PLAY_PAUSE); return _isPlaying; } else if(_mediaSource != "") { load(_mediaSource, _type, _streamType, _server); callEvents(PlayerEvents.BUFFERING); return true; } callEvents(PlayerEvents.PLAY_PAUSE); return false; } /** * Switches on or off fullscreen * @return true if fullscreen otherwise false */ public function toggleFullscreen():Bool { if (_fullscreen) { _stage.displayState = StageDisplayState.NORMAL; _stage.focus = _stage; return false; } else { if (_useHardWareScaling) { //Match full screen aspec ratio to desktop var aspectRatio = Capabilities.screenResolutionY / Capabilities.screenResolutionX; _stage.fullScreenSourceRect = new Rectangle(0, 0, _videoWidth, _videoWidth * aspectRatio); } else { //Use desktop resolution _stage.fullScreenSourceRect = new Rectangle(0, 0, Capabilities.screenResolutionX ,Capabilities.screenResolutionY); } _stage.displayState = StageDisplayState.FULL_SCREEN; _stage.focus = _stage; return true; } } /** * Toggles betewen high and low quality image rendering * @return true if quality high false otherwise */ public function toggleQuality():Bool { if (_videoQualityHigh) { _video.smoothing = false; _video.deblocking = 1; } else { _video.smoothing = true; _video.deblocking = 5; } _videoQualityHigh = _videoQualityHigh?false:true; return _videoQualityHigh; } /** * Mutes or unmutes the sound * @return true if muted false if unmuted */ public function toggleMute():Bool { var soundTransform:SoundTransform = new SoundTransform(); var isMute:Bool; //unmute sound if (_soundMuted) { _soundMuted = false; if (_volume > 0) { soundTransform.volume = _volume; } else { _volume = 1.0; soundTransform.volume = _volume; } isMute = false; } //mute sound else { _soundMuted = true; _volume = _stream.soundTransform.volume; soundTransform.volume = 0; _stream.soundTransform = soundTransform; isMute = true; } if (_streamType == StreamType.YOUTUBE) { Reflect.field(_youtubeLoader.content, "setVolume")(soundTransform.volume * 100); } else if (_type == InputType.VIDEO || _streamType == StreamType.RTMP) { _stream.soundTransform = soundTransform; } else if (_type == InputType.AUDIO) { _soundChannel.soundTransform = soundTransform; setVolume(_userSettings.getVolume()); } callEvents(PlayerEvents.MUTE); return isMute; } /** * Check if player is running on fullscreen mode * @return true if fullscreen false if not */ public function isFullscreen():Bool { return _stage.displayState == StageDisplayState.FULL_SCREEN; } /** * Raises the volume * @return volume value after raising */ public function volumeUp():Float { var soundTransform:SoundTransform = new SoundTransform(); if (_soundMuted) { _soundMuted = false; } //raise volume if not already at max if (_volume < 1) { if (_streamType == StreamType.YOUTUBE) { _volume = (Reflect.field(_youtubeLoader.content, "getVolume")() + 10) / 100; Reflect.field(_youtubeLoader.content, "setVolume")(_volume * 100); } else if (_type == InputType.VIDEO || _streamType == StreamType.RTMP) { _volume = _stream.soundTransform.volume + (10/100); soundTransform.volume = _volume; _stream.soundTransform = soundTransform; } else if (_type == InputType.AUDIO) { _volume = _soundChannel.soundTransform.volume + (10/100); soundTransform.volume = _volume; _soundChannel.soundTransform = soundTransform; } } //reset volume to 1.0 if already reached max if (_volume >= 1) { _volume = 1.0; } //Store volume into user settings _userSettings.setVolume(_volume); callEvents(PlayerEvents.VOLUME_UP); return _volume; } /** * Lower the volume * @return volume value after lowering */ public function volumeDown():Float { var soundTransform:SoundTransform = new SoundTransform(); //lower sound if(!_soundMuted) { if (_streamType == StreamType.YOUTUBE) { _volume = (Reflect.field(_youtubeLoader.content, "getVolume")() - 10) / 100; Reflect.field(_youtubeLoader.content, "setVolume")(_volume * 100); } else if (_type == InputType.VIDEO || _streamType == StreamType.RTMP) { _volume = _stream.soundTransform.volume - (10/100); soundTransform.volume = _volume; _stream.soundTransform = soundTransform; } else if (_type == InputType.AUDIO) { _volume = _soundChannel.soundTransform.volume - (10/100); soundTransform.volume = _volume; _soundChannel.soundTransform = soundTransform; } //if volume reached min is muted if (_volume <= 0) { _soundMuted = true; _volume = 0; } } //Store volume into user settings _userSettings.setVolume(_volume); callEvents(PlayerEvents.VOLUME_DOWN); return _volume; } //} //{Setters /** * Set input type * @param type Allowable values are audio, video */ public function setType(type:String):Void { _type = type; } /** * Set streaming type * @param streamType Allowable values are file, http, rmtp */ public function setStreamType(streamType:String):Void { _streamType = streamType; } /** * Sets the server url for rtmp streams * @param server */ public function setServer(server:String):Void { _server = server; } /** * To set the video source in case we dont want to start downloading at first so when use tooglePlay the * media is loaded automatically * @param source */ public function setSource(source):Void { _mediaSource = source; } /** * Changes the current volume * @param volume */ public function setVolume(volume:Float):Void { var soundTransform:SoundTransform = new SoundTransform(); if (volume > _volume) { callEvents(PlayerEvents.VOLUME_UP); } if (volume < _volume) { callEvents(PlayerEvents.VOLUME_DOWN); } if (volume > 0) { _soundMuted = false; _volume = volume; } else { _soundMuted = true; _volume = 1.0; } soundTransform.volume = volume; if (!_firstLoad) //To prevent errors if objects aren't initialized { if (_streamType == StreamType.YOUTUBE) { Reflect.field(_youtubeLoader.content, "setVolume")(soundTransform.volume * 100); } else if (_type == InputType.VIDEO || _streamType == StreamType.RTMP) { _stream.soundTransform = soundTransform; } else if (_type == InputType.AUDIO) { _soundChannel.soundTransform = soundTransform; } } //Store volume into user settings _userSettings.setVolume(_volume); callEvents(PlayerEvents.VOLUME_CHANGE); } /** * Changes the buffer time for local and pseudo streaming * @param time in seconds */ public function setBufferTime(time:Float):Void { if (time > 0) { _bufferTime = time; } } /** * Changes the aspec ratio of current playing media and resizes video player * @param aspectRatio new aspect ratio value */ public function setAspectRatio(aspectRatio:Float):Void { _aspectRatio = aspectRatio; switch(_aspectRatio) { case 0.0: _currentAspectRatio = "original"; case AspectRatio._1_1: _currentAspectRatio = "1:1"; case AspectRatio._3_2: _currentAspectRatio = "3:2"; case AspectRatio._4_3: _currentAspectRatio = "4:3"; case AspectRatio._5_4: _currentAspectRatio = "5:4"; case AspectRatio._14_9: _currentAspectRatio = "14:9"; case AspectRatio._14_10: _currentAspectRatio = "14:10"; case AspectRatio._16_9: _currentAspectRatio = "16:9"; case AspectRatio._16_10: _currentAspectRatio = "16:10"; } resizeAndCenterPlayer(); //Store aspect ratio into user settings _userSettings.setAspectRatio(_aspectRatio); } /** * Enable or disable hardware scaling * @param value true to enable false to disable */ public function setHardwareScaling(value:Bool):Void { _useHardWareScaling = value; } //} //{Getters /** * Gets the volume amount 0.0 to 1.0 * @return */ public function getVolume():Float { return _volume; } /** * The current aspect ratio of the loaded Player * @return */ public function getAspectRatio():Float { return _aspectRatio; } /** * The current aspect ratio of the loaded Player in string format * @return */ public function getAspectRatioString():String { return _currentAspectRatio; } /** * Original aspect ratio of the video * @return original aspect ratio */ public function getOriginalAspectRatio():Float { return _originalAspectRatio; } /** * Total duration time of the loaded media * @return time in seconds */ public function getDuration():Float { return _mediaDuration; } /** * The time in seconds where the player started downloading * @return time in seconds */ public function getStartTime():Float { return _startTime; } /** * The stream associated with the player * @return netstream object */ public function getNetStream():NetStream { return _stream; } /** * Video object associated to the player * @return video object for further manipulation */ public function getVideo():Video { return _video; } /** * Sound object associated to the player * @return sound object for further manipulation */ public function getSound():Sound { return _sound; } /** * The id3 info of sound object * @return */ public function getId3Info():ID3Info { return _id3Info; } /** * The current sound state * @return true if mute otherwise false */ public function getMute():Bool { return _soundMuted; } /** * The amount of total bytes * @return amount of bytes */ public function getBytesTotal():Float { var bytesTotal:Float = 0; if (_streamType == StreamType.YOUTUBE) { if(_youtubeLoader != null && _mediaLoaded) bytesTotal = Reflect.field(_youtubeLoader.content, "getVideoBytesTotal")(); } else if (_type == InputType.VIDEO || _streamType == StreamType.RTMP) { bytesTotal = _stream.bytesTotal; } else if (_type == InputType.AUDIO) { bytesTotal = _sound.bytesTotal; } return bytesTotal; } /** * The amount of bytes loaded * @return amount of bytes */ public function getBytesLoaded():Float { var bytesLoaded:Float = 0; if (_streamType == StreamType.YOUTUBE) { if(_youtubeLoader != null && _mediaLoaded) bytesLoaded = Reflect.field(_youtubeLoader.content, "getVideoBytesLoaded")(); } else if (_type == InputType.VIDEO || _streamType == StreamType.RTMP) { bytesLoaded = _stream.bytesLoaded; } else if (_type == InputType.AUDIO) { bytesLoaded = _sound.bytesLoaded; } return bytesLoaded; } /** * Current playing file type * @return audio or video */ public function getType():String { return _type; } /** * The stream method for the current playing media * @return */ public function getStreamType():String { return _streamType; } /** * The server url for current rtmp stream * @return */ public function getServer():String { return _server; } /** * To check current quality mode * @return true if high quality false if low */ public function getQuality():Bool { return _videoQualityHigh; } /** * The current playing time * @return current playing time in seconds */ public function getCurrentTime():Float { var time:Float = 0; if (_streamType == StreamType.YOUTUBE) { if(_youtubeLoader != null) { time = Reflect.field(_youtubeLoader.content, "getCurrentTime")(); } else { time = 0; } } else if (_streamType == StreamType.PSEUDOSTREAM) { time = getStartTime() + _stream.time; } else if (_type == InputType.VIDEO || _streamType == StreamType.RTMP) { time = _stream.time; } else if (_type == InputType.AUDIO) { if(_soundChannel != null) { time = _soundChannel.position / 1000; } else { time = 0; } } return time; } //} }