gyorgy@0: package htmlelements
gyorgy@0: {
gyorgy@0: import flash.display.Sprite;
gyorgy@0: import flash.events.*;
gyorgy@0: import flash.net.NetConnection;
gyorgy@0: import flash.net.NetStream;
gyorgy@0: import flash.media.Video;
gyorgy@0: import flash.media.SoundTransform;
gyorgy@0: import flash.utils.Timer;
gyorgy@0:
gyorgy@0: import FlashMediaElement;
gyorgy@0: import HtmlMediaEvent;
gyorgy@0:
gyorgy@0: public class VideoElement extends Sprite implements IMediaElement
gyorgy@0: {
gyorgy@0: private var _currentUrl:String = "";
gyorgy@0: private var _autoplay:Boolean = true;
gyorgy@0: private var _preload:String = "";
gyorgy@0:
gyorgy@0: private var _connection:NetConnection;
gyorgy@0: private var _stream:NetStream;
gyorgy@0: private var _video:Video;
gyorgy@0: private var _element:FlashMediaElement;
gyorgy@0: private var _soundTransform;
gyorgy@0: private var _oldVolume:Number = 1;
gyorgy@0:
gyorgy@0: // event values
gyorgy@0: private var _duration:Number = 0;
gyorgy@0: private var _framerate:Number;
gyorgy@0: private var _isPaused:Boolean = true;
gyorgy@0: private var _isEnded:Boolean = false;
gyorgy@0: private var _volume:Number = 1;
gyorgy@0: private var _isMuted:Boolean = false;
gyorgy@0:
gyorgy@0: private var _bytesLoaded:Number = 0;
gyorgy@0: private var _bytesTotal:Number = 0;
gyorgy@0: private var _bufferedTime:Number = 0;
gyorgy@0: private var _bufferEmpty:Boolean = false;
gyorgy@0:
gyorgy@0: private var _videoWidth:Number = -1;
gyorgy@0: private var _videoHeight:Number = -1;
gyorgy@0:
gyorgy@0: private var _timer:Timer;
gyorgy@0:
gyorgy@0: private var _isRTMP:Boolean = false;
gyorgy@0: private var _isConnected:Boolean = false;
gyorgy@0: private var _playWhenConnected:Boolean = false;
gyorgy@0: private var _hasStartedPlaying:Boolean = false;
gyorgy@0:
gyorgy@0: public function get video():Video {
gyorgy@0: return _video;
gyorgy@0: }
gyorgy@0:
gyorgy@0: public function get videoHeight():Number {
gyorgy@0: return _videoHeight;
gyorgy@0: }
gyorgy@0:
gyorgy@0: public function get videoWidth():Number {
gyorgy@0: return _videoWidth;
gyorgy@0: }
gyorgy@0:
gyorgy@0:
gyorgy@0: public function duration():Number {
gyorgy@0: return _duration;
gyorgy@0: }
gyorgy@0:
gyorgy@0: public function currentTime():Number {
gyorgy@0: if (_stream != null) {
gyorgy@0: return _stream.time;
gyorgy@0: } else {
gyorgy@0: return 0;
gyorgy@0: }
gyorgy@0: }
gyorgy@0:
gyorgy@0: // (1) load()
gyorgy@0: // calls _connection.connect();
gyorgy@0: // waits for NetConnection.Connect.Success
gyorgy@0: // _stream gets created
gyorgy@0:
gyorgy@0:
gyorgy@0: public function VideoElement(element:FlashMediaElement, autoplay:Boolean, preload:String, timerRate:Number, startVolume:Number)
gyorgy@0: {
gyorgy@0: _element = element;
gyorgy@0: _autoplay = autoplay;
gyorgy@0: _volume = startVolume;
gyorgy@0: _preload = preload;
gyorgy@0:
gyorgy@0: _video = new Video();
gyorgy@0: addChild(_video);
gyorgy@0:
gyorgy@0: _connection = new NetConnection();
gyorgy@0: _connection.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
gyorgy@0: _connection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
gyorgy@0: //_connection.connect(null);
gyorgy@0:
gyorgy@0: _timer = new Timer(timerRate);
gyorgy@0: _timer.addEventListener("timer", timerHandler);
gyorgy@0:
gyorgy@0: }
gyorgy@0:
gyorgy@0: private function timerHandler(e:TimerEvent) {
gyorgy@0:
gyorgy@0: _bytesLoaded = _stream.bytesLoaded;
gyorgy@0: _bytesTotal = _stream.bytesTotal;
gyorgy@0:
gyorgy@0: sendEvent(HtmlMediaEvent.TIMEUPDATE);
gyorgy@0:
gyorgy@0: trace("bytes", _bytesLoaded, _bytesTotal);
gyorgy@0:
gyorgy@0: if (_bytesLoaded < _bytesTotal)
gyorgy@0: sendEvent(HtmlMediaEvent.PROGRESS);
gyorgy@0: }
gyorgy@0:
gyorgy@0: // internal events
gyorgy@0: private function netStatusHandler(event:NetStatusEvent):void {
gyorgy@0: trace("netStatus", event.info.code);
gyorgy@0:
gyorgy@0: switch (event.info.code) {
gyorgy@0:
gyorgy@0: case "NetStream.Buffer.Empty":
gyorgy@0: _bufferEmpty = true;
gyorgy@0: _isEnded ? sendEvent(HtmlMediaEvent.ENDED) : null;
gyorgy@0: break;
gyorgy@0:
gyorgy@0: case "NetStream.Buffer.Full":
gyorgy@0: _bytesLoaded = _stream.bytesLoaded;
gyorgy@0: _bytesTotal = _stream.bytesTotal;
gyorgy@0: _bufferEmpty = false;
gyorgy@0:
gyorgy@0: sendEvent(HtmlMediaEvent.PROGRESS);
gyorgy@0: break;
gyorgy@0:
gyorgy@0: case "NetConnection.Connect.Success":
gyorgy@0: connectStream();
gyorgy@0: break;
gyorgy@0: case "NetStream.Play.StreamNotFound":
gyorgy@0: trace("Unable to locate video");
gyorgy@0: break;
gyorgy@0:
gyorgy@0: // STREAM
gyorgy@0: case "NetStream.Play.Start":
gyorgy@0: _isPaused = false;
gyorgy@0: sendEvent(HtmlMediaEvent.LOADEDDATA);
gyorgy@0: sendEvent(HtmlMediaEvent.CANPLAY);
gyorgy@0: sendEvent(HtmlMediaEvent.PLAY);
gyorgy@0: sendEvent(HtmlMediaEvent.PLAYING);
gyorgy@0: _timer.start();
gyorgy@0: break;
gyorgy@0:
gyorgy@0: case "NetStream.Seek.Notify":
gyorgy@0: sendEvent(HtmlMediaEvent.SEEKED);
gyorgy@0: break;
gyorgy@0:
gyorgy@0: case "NetStream.Pause.Notify":
gyorgy@0: _isPaused = true;
gyorgy@0: sendEvent(HtmlMediaEvent.PAUSE);
gyorgy@0: break;
gyorgy@0:
gyorgy@0: case "NetStream.Play.Stop":
gyorgy@0: _isEnded = true;
gyorgy@0: _isPaused = false;
gyorgy@0: _timer.stop();
gyorgy@0: _bufferEmpty ? sendEvent(HtmlMediaEvent.ENDED) : null;
gyorgy@0: break;
gyorgy@0:
gyorgy@0: }
gyorgy@0: }
gyorgy@0:
gyorgy@0:
gyorgy@0: private function securityErrorHandler(event:SecurityErrorEvent):void {
gyorgy@0: trace("securityErrorHandler: " + event);
gyorgy@0: }
gyorgy@0:
gyorgy@0: private function asyncErrorHandler(event:AsyncErrorEvent):void {
gyorgy@0: // ignore AsyncErrorEvent events.
gyorgy@0: }
gyorgy@0:
gyorgy@0:
gyorgy@0: private function onMetaDataHandler(info:Object):void {
gyorgy@0: _duration = info.duration;
gyorgy@0: _framerate = info.framerate;
gyorgy@0: _videoWidth = info.width;
gyorgy@0: _videoHeight = info.height;
gyorgy@0:
gyorgy@0: // set size?
gyorgy@0:
gyorgy@0: sendEvent(HtmlMediaEvent.LOADEDMETADATA);
gyorgy@0: }
gyorgy@0:
gyorgy@0:
gyorgy@0:
gyorgy@0:
gyorgy@0: // interface members
gyorgy@0: public function setSrc(url:String):void {
gyorgy@0: if (_isConnected && _stream) {
gyorgy@0: // stop and restart
gyorgy@0: _stream.pause();
gyorgy@0: }
gyorgy@0:
gyorgy@0: _currentUrl = url;
gyorgy@0: _isRTMP = !!_currentUrl.match(/^rtmp(s|t|e|te)?\:\/\//);
gyorgy@0: _isConnected = false;
gyorgy@0: _hasStartedPlaying = false;
gyorgy@0: }
gyorgy@0:
gyorgy@0: public function load():void {
gyorgy@0: // disconnect existing stream and connection
gyorgy@0: if (_isConnected && _stream) {
gyorgy@0: _stream.pause();
gyorgy@0: _stream.close();
gyorgy@0: _connection.close();
gyorgy@0: }
gyorgy@0: _isConnected = false;
gyorgy@0:
gyorgy@0: // start new connection
gyorgy@0: if (_isRTMP) {
gyorgy@0: _connection.connect(_currentUrl.replace(/\/[^\/]+$/,"/"));
gyorgy@0: } else {
gyorgy@0: _connection.connect(null);
gyorgy@0: }
gyorgy@0:
gyorgy@0: // in a few moments the "NetConnection.Connect.Success" event will fire
gyorgy@0: // and call createConnection which finishes the "load" sequence
gyorgy@0: sendEvent(HtmlMediaEvent.LOADSTART);
gyorgy@0: }
gyorgy@0:
gyorgy@0:
gyorgy@0: private function connectStream():void {
gyorgy@0: trace("connectStream");
gyorgy@0: _stream = new NetStream(_connection);
gyorgy@0:
gyorgy@0: // explicitly set the sound since it could have come before the connection was made
gyorgy@0: _soundTransform = new SoundTransform(_volume);
gyorgy@0: _stream.soundTransform = _soundTransform;
gyorgy@0:
gyorgy@0: _stream.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler); // same event as connection
gyorgy@0: _stream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler);
gyorgy@0:
gyorgy@0: var customClient:Object = new Object();
gyorgy@0: customClient.onMetaData = onMetaDataHandler;
gyorgy@0: _stream.client = customClient;
gyorgy@0:
gyorgy@0: _video.attachNetStream(_stream);
gyorgy@0:
gyorgy@0: // start downloading without playing )based on preload and play() hasn't been called)
gyorgy@0: // I wish flash had a load() command to make this less awkward
gyorgy@0: if (_preload != "none" && !_playWhenConnected) {
gyorgy@0: _stream.play(_currentUrl, 0, 0);
gyorgy@0:
gyorgy@0: _stream.pause();
gyorgy@0: _isPaused = true;
gyorgy@0: sendEvent(HtmlMediaEvent.PAUSE); // have to send this because the "playing" event gets sent via event handlers
gyorgy@0: }
gyorgy@0:
gyorgy@0: _isConnected = true;
gyorgy@0:
gyorgy@0: if (_playWhenConnected && !_hasStartedPlaying) {
gyorgy@0: play();
gyorgy@0: _playWhenConnected = false;
gyorgy@0: }
gyorgy@0:
gyorgy@0: }
gyorgy@0:
gyorgy@0: public function play():void {
gyorgy@0:
gyorgy@0: if (!_hasStartedPlaying && !_isConnected) {
gyorgy@0: _playWhenConnected = true;
gyorgy@0: load();
gyorgy@0: return;
gyorgy@0: }
gyorgy@0:
gyorgy@0: if (_hasStartedPlaying) {
gyorgy@0: if (_isPaused) {
gyorgy@0: _stream.resume();
gyorgy@0: _timer.start();
gyorgy@0: _isPaused = false;
gyorgy@0: sendEvent(HtmlMediaEvent.PLAY);
gyorgy@0: sendEvent(HtmlMediaEvent.PLAYING);
gyorgy@0: }
gyorgy@0: } else {
gyorgy@0:
gyorgy@0: if (_isRTMP) {
gyorgy@0: _stream.play(_currentUrl.split("/").pop());
gyorgy@0: } else {
gyorgy@0: _stream.play(_currentUrl);
gyorgy@0: }
gyorgy@0: _timer.start();
gyorgy@0: _isPaused = false;
gyorgy@0: _hasStartedPlaying = true;
gyorgy@0:
gyorgy@0: // don't toss play/playing events here, because we haven't sent a
gyorgy@0: // canplay / loadeddata event yet. that'll be handled in the net
gyorgy@0: // event listener
gyorgy@0: }
gyorgy@0:
gyorgy@0: }
gyorgy@0:
gyorgy@0: public function pause():void {
gyorgy@0: if (_stream == null)
gyorgy@0: return;
gyorgy@0:
gyorgy@0: _stream.pause();
gyorgy@0: _isPaused = true;
gyorgy@0: _timer.stop();
gyorgy@0:
gyorgy@0: _isPaused = true;
gyorgy@0: sendEvent(HtmlMediaEvent.PAUSE);
gyorgy@0: }
gyorgy@0:
gyorgy@0: public function stop():void {
gyorgy@0: if (_stream == null)
gyorgy@0: return;
gyorgy@0:
gyorgy@0: _stream.close();
gyorgy@0: _isPaused = false;
gyorgy@0: _timer.stop();
gyorgy@0: sendEvent(HtmlMediaEvent.STOP);
gyorgy@0: }
gyorgy@0:
gyorgy@0: public function setCurrentTime(pos:Number):void {
gyorgy@0: if (_stream == null)
gyorgy@0: return;
gyorgy@0:
gyorgy@0: sendEvent(HtmlMediaEvent.SEEKING);
gyorgy@0: _stream.seek(pos);
gyorgy@0: sendEvent(HtmlMediaEvent.TIMEUPDATE);
gyorgy@0: }
gyorgy@0:
gyorgy@0: public function setVolume(volume:Number):void {
gyorgy@0: if (_stream != null) {
gyorgy@0: _soundTransform = new SoundTransform(volume);
gyorgy@0: _stream.soundTransform = _soundTransform;
gyorgy@0: }
gyorgy@0:
gyorgy@0: _volume = volume;
gyorgy@0:
gyorgy@0: _isMuted = (_volume == 0);
gyorgy@0:
gyorgy@0: sendEvent(HtmlMediaEvent.VOLUMECHANGE);
gyorgy@0: }
gyorgy@0:
gyorgy@0:
gyorgy@0: public function setMuted(muted:Boolean):void {
gyorgy@0:
gyorgy@0: if (_isMuted == muted)
gyorgy@0: return;
gyorgy@0:
gyorgy@0: if (muted) {
gyorgy@0: _oldVolume = _stream.soundTransform.volume;
gyorgy@0: setVolume(0);
gyorgy@0: } else {
gyorgy@0: setVolume(_oldVolume);
gyorgy@0: }
gyorgy@0:
gyorgy@0: _isMuted = muted;
gyorgy@0: }
gyorgy@0:
gyorgy@0:
gyorgy@0: private function sendEvent(eventName:String) {
gyorgy@0:
gyorgy@0: // calculate this to mimic HTML5
gyorgy@0: _bufferedTime = _bytesLoaded / _bytesTotal * _duration;
gyorgy@0:
gyorgy@0: // build JSON
gyorgy@0: var values:String =
gyorgy@0: "duration:" + _duration +
gyorgy@0: ",framerate:" + _framerate +
gyorgy@0: ",currentTime:" + (_stream != null ? _stream.time : 0) +
gyorgy@0: ",muted:" + _isMuted +
gyorgy@0: ",paused:" + _isPaused +
gyorgy@0: ",ended:" + _isEnded +
gyorgy@0: ",volume:" + _volume +
gyorgy@0: ",src:\"" + _currentUrl + "\"" +
gyorgy@0: ",bytesTotal:" + _bytesTotal +
gyorgy@0: ",bufferedBytes:" + _bytesLoaded +
gyorgy@0: ",bufferedTime:" + _bufferedTime +
gyorgy@0: ",videoWidth:" + _videoWidth +
gyorgy@0: ",videoHeight:" + _videoHeight +
gyorgy@0: "";
gyorgy@0:
gyorgy@0: _element.sendEvent(eventName, values);
gyorgy@0: }
gyorgy@0: }
gyorgy@0: }