From 778c605577f4c1e79304940aa4ee4ddeb32aa706 Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Mon, 1 Jun 2015 10:07:42 -0500 Subject: [PATCH 1/4] Playing FLVs using VP6Player --- examples/inspector/js/inspectorGfx.js | 1 + extension/firefox/Makefile | 2 + .../content/ShumwayStreamConverter.jsm | 5 + src/flash/net/NetStream.ts | 16 +- src/gfx/references-base.ts | 1 + src/gfx/renderables/renderables.ts | 34 ++- src/gfx/vp6player.ts | 269 ++++++++++++++++++ .../org/mozilla/vp6player/VP6Player.as | 185 ++++++++++++ utils/vp6player/vp6player.fla | Bin 0 -> 4704 bytes utils/vp6player/vp6player.swf | Bin 0 -> 2537 bytes 10 files changed, 499 insertions(+), 14 deletions(-) create mode 100644 src/gfx/vp6player.ts create mode 100644 utils/vp6player/org/mozilla/vp6player/VP6Player.as create mode 100644 utils/vp6player/vp6player.fla create mode 100755 utils/vp6player/vp6player.swf diff --git a/examples/inspector/js/inspectorGfx.js b/examples/inspector/js/inspectorGfx.js index 7d6b34ad87..b1a21e01c2 100644 --- a/examples/inspector/js/inspectorGfx.js +++ b/examples/inspector/js/inspectorGfx.js @@ -24,6 +24,7 @@ function createPlaybackEaselHost(file) { var easel; function createEasel() { + Shumway.GFX.VP6Player.SWFPath = "../../utils/vp6player/vp6player.swf"; Shumway.GFX.WebGL.SHADER_ROOT = "../../src/gfx/gl/shaders/"; easel = new Easel(document.getElementById("easelContainer")); easel.startRendering(); diff --git a/extension/firefox/Makefile b/extension/firefox/Makefile index 0396b92ff2..2dac5153f0 100644 --- a/extension/firefox/Makefile +++ b/extension/firefox/Makefile @@ -42,6 +42,7 @@ build: ensureoutputdir mkdir -p $(BUILD_DIR)/content/libs cp ../../build/libs/builtin.abc $(BUILD_DIR)/content/libs/ cp ../../build/libs/relooper.js $(BUILD_DIR)/content/libs/ + cp ../../utils/vp6player/vp6player.swf $(BUILD_DIR)/content/ # Copying closure optimized shumway.js files cp $(BUNDLES_DIR)/shumway*.js $(BUILD_DIR)/content/ cp ../../build/version/version.txt $(BUILD_DIR)/content/version.txt @@ -71,6 +72,7 @@ restartless: ln -s ../../../build/libs $(RESTARTLESS_DIR)/content/libs ln -s ../../../build/bundles/shumway.gfx.js $(RESTARTLESS_DIR)/content/shumway.gfx.js ln -s ../../../build/bundles/shumway.player.js $(RESTARTLESS_DIR)/content/shumway.player.js + ln -s ../../../utils/vp6player/vp6player.swf $(RESTARTLESS_DIR)/content/vp6player.swf ln -s ../../../build/version/version.txt $(RESTARTLESS_DIR)/content/version.txt cd $(RESTARTLESS_DIR); pwd > "$(PROFILE)/extensions/shumway@research.mozilla.org" grep "nglayout.debug.disable_xul_cache\", true" "$(PROFILE)/prefs.js" 1>/dev/null || echo "Set create 'nglayout.debug.disable_xul_cache' boolean preference to 'true'." diff --git a/extension/firefox/content/ShumwayStreamConverter.jsm b/extension/firefox/content/ShumwayStreamConverter.jsm index 58c910f5e0..b43ad7a4e4 100644 --- a/extension/firefox/content/ShumwayStreamConverter.jsm +++ b/extension/firefox/content/ShumwayStreamConverter.jsm @@ -106,6 +106,11 @@ function isShumwayEnabledFor(startupInfo) { var url = startupInfo.url; var baseUrl = startupInfo.baseUrl; + if (/^resource:\/\/shumway\//i.test(url)) { + // disabled for internal Shumway SWFs + return false; + } + // blacklisting well known sites with issues if (/\.ytimg\.com\//i.test(url) /* youtube movies */ || /\/vui.swf\b/i.test(url) /* vidyo manager */ || diff --git a/src/flash/net/NetStream.ts b/src/flash/net/NetStream.ts index 4f41edc75a..bfaab4f9ab 100644 --- a/src/flash/net/NetStream.ts +++ b/src/flash/net/NetStream.ts @@ -639,12 +639,18 @@ module Shumway.AVMX.AS.flash.net { Debug.warning('MediaSource API is not enabled, falling back to regular playback'); isMediaSourceEnabled = false; } + var flvMode: string = flvOption.value; var forceMediaSource = false; - if (/\.flv($|\?)/i.test(url)) { - if (flvOption.value === 'supported') { + var useVP6Player = false; + if (flvMode === 'alwaysflash') { + useVP6Player = true; + } else if (/\.flv($|\?)/i.test(url)) { + if (flvMode === 'supported') { forceMediaSource = true; - } else if (flvOption.value === 'mock') { + } else if (flvMode === 'mock') { url = 'resource://shumway/web/noflv.mp4'; + } else if (flvMode === 'flash') { + useVP6Player = true; } else { setTimeout(() => { this._netStream.dispatchEvent(new this.sec.flash.events.NetStatusEvent(events.NetStatusEvent.NET_STATUS, @@ -653,10 +659,14 @@ module Shumway.AVMX.AS.flash.net { return; } } + if (!forceMediaSource && !isMediaSourceEnabled) { somewhatImplemented("public flash.net.NetStream::play"); this._state = VideoStreamState.OPENED; this._url = FileLoadingService.instance.resolveUrl(url); + if (useVP6Player) { + this._url = 'vp6:' + this._url; + } return; } diff --git a/src/gfx/references-base.ts b/src/gfx/references-base.ts index 739a4f84c7..01f5a7664a 100644 --- a/src/gfx/references-base.ts +++ b/src/gfx/references-base.ts @@ -23,6 +23,7 @@ /// /// /// +/// /// /// diff --git a/src/gfx/renderables/renderables.ts b/src/gfx/renderables/renderables.ts index 50a661fa52..1684bae677 100644 --- a/src/gfx/renderables/renderables.ts +++ b/src/gfx/renderables/renderables.ts @@ -212,7 +212,23 @@ module Shumway.GFX { this._assetId = assetId; this._eventSerializer = eventSerializer; - var element: HTMLVideoElement = document.createElement('video'); + RenderableVideo._renderableVideos.push(this); + + if (typeof registerInspectorAsset !== "undefined") { + registerInspectorAsset(-1, -1, this); + } + + this._state = RenderableVideoState.Idle; + } + + private _setupVideoElement(useFlash: boolean): HTMLVideoElement { + var element: HTMLVideoElement; + if (useFlash) { + element = VP6Player.createDOMElement(); + } else { + element = document.createElement('video'); + } + var elementEventHandler = this._handleVideoEvent.bind(this); element.preload = 'metadata'; // for mobile devices element.addEventListener("play", elementEventHandler); @@ -230,14 +246,7 @@ module Shumway.GFX { this._video = element; this._videoEventHandler = elementEventHandler; - - RenderableVideo._renderableVideos.push(this); - - if (typeof registerInspectorAsset !== "undefined") { - registerInspectorAsset(-1, -1, this); - } - - this._state = RenderableVideoState.Idle; + return element; } public get video(): HTMLVideoElement { @@ -342,7 +351,9 @@ module Shumway.GFX { var ESTIMATED_VIDEO_SECOND_SIZE: number = 500; switch (type) { case VideoControlEvent.Init: - videoElement.src = data.url; + var useVP6Player = data.url && data.url.indexOf('vp6:') === 0; + videoElement = this._setupVideoElement(useVP6Player); + videoElement.src = useVP6Player ? data.url.substring('vp6:'.length) : data.url; this.play(); this._notifyNetStream(VideoPlaybackEvent.Initialized, null); break; @@ -422,7 +433,8 @@ module Shumway.GFX { if (renderable.willRender()) { // If the nodes video element isn't already on the video layer, mark the node as invalid to // make sure the video element will be added the next time the renderer reaches it. - if (!renderable._video.parentElement) { + if (!renderable._video.parentElement || + renderable._video.parentElement === VP6Player.hiddenLayer) { renderable.invalidate(); } renderable._video.style.zIndex = renderable.parents[0].depth + ''; diff --git a/src/gfx/vp6player.ts b/src/gfx/vp6player.ts new file mode 100644 index 0000000000..04707cfc49 --- /dev/null +++ b/src/gfx/vp6player.ts @@ -0,0 +1,269 @@ +/** + * Copyright 2015 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +module Shumway.GFX { + + var objectID = 0; + function generateUniqueID(): string { + return '_vp6player' + (objectID++); + } + + var hiddenVP6PlayersLayer: HTMLElement; + function getHiddenVP6PlayersLayer() { + if (!hiddenVP6PlayersLayer) { + hiddenVP6PlayersLayer = document.createElement('div'); + hiddenVP6PlayersLayer.setAttribute('style', 'position:absolute;width:0;height:0;overflow:hidden'); + document.body.appendChild(hiddenVP6PlayersLayer); + } + return hiddenVP6PlayersLayer; + } + + export class VP6Player { + public static SWFPath: string = 'resource://shumway/vp6player.swf'; + + private _obj: HTMLObjectElement | any; + private _shadowObj: any; + private _initialized: boolean; + private _playing: boolean; + private _errorObj: {code: number}; + private _seekableObj: {length: number}; + private _bufferedObj: {length: number; end: (index: number) => number}; + + public constructor() { + var id = generateUniqueID(); + var proxy = this; + var obj = document.createElement('object'), param; + obj.id = id; + obj.setAttribute('type', 'application/x-shockwave-flash'); + obj.setAttribute('data', VP6Player.SWFPath); + // FIXME we still need to add file:/// path to vp6player.swf in the profile to the trusted Flash locations + param = document.createElement('param'); + param.setAttribute('name', 'allowScriptAccess'); + param.setAttribute('value', 'always'); + obj.appendChild(param); + param = document.createElement('param'); + param.setAttribute('name', 'scale'); + param.setAttribute('value', 'exactfit'); + obj.appendChild(param); + param = document.createElement('param'); + param.setAttribute('name', 'flashvars'); + param.setAttribute('value', 'id=' + id); + obj.appendChild(param); + param = document.createElement('param'); + param.setAttribute('name', 'shumwaymode'); + param.setAttribute('value', 'off'); + obj.appendChild(param); + + this._obj = obj; + this._shadowObj = Object.create(null); + this._initialized = false; + this._playing = false; + + this._errorObj = { + get code() { + return proxy._shadowObj.errorCode; + } + }; + this._seekableObj = { + get length() { + return proxy._initialized ? 1 : 0; + } + }; + this._bufferedObj = { + get length() { + return proxy._initialized ? 1 : 0; + }, + end: function (index) { + return this._obj._getBufferedTime(); + } + }; + + this._obj._proxy = this; + } + + public static createDOMElement(): HTMLElement { + var div = document.createElement('div'); + div.style.display = 'inline-block'; + VP6Player.call(div); + var propertiesToTransfer = ['width', 'height', 'src', 'currentTime', + 'volume', 'error', 'videoWidth', 'videoHeight', 'paused', 'seekable', + 'buffered', 'play', 'pause', '_dispatchEvent', '_pushProperty', + '_syncProperties', '_init', '_setSize']; + propertiesToTransfer.forEach((x) => { + var desc = Object.getOwnPropertyDescriptor(VP6Player.prototype, x); + Debug.assert(desc); + Object.defineProperty(div, x, desc); + }); + div.appendChild((div)._obj); + getHiddenVP6PlayersLayer().appendChild(div); + return div; + } + + public get width() { + return this._obj.width; + } + + public set width(value) { + this._obj.width = value; + } + + public get height() { + return this._obj.height; + } + + public set height(value) { + this._obj.height = value; + } + + public get src(): string { + return this._shadowObj.src; + } + + public set src(value: string) { + this._shadowObj.src = value; + this._pushProperty('src'); + } + + public get currentTime(): number { + if (!this._initialized) { + return NaN; + } + return this._obj.getTime(); + } + + public set currentTime(value: number) { + this._obj.setTime(value); + } + + public get volume(): number { + return this._shadowObj.volume; + } + + public set volume(value: number) { + this._shadowObj.volume = value; + this._pushProperty('volume'); + } + + public get error() { + return this._errorObj; + } + + public get videoWidth(): number { + return this._shadowObj.videoWidth; + } + + public get videoHeight(): number { + return this._shadowObj.videoHeight; + } + + public get duration(): number { + return this._shadowObj.duration; + } + + public get paused(): boolean { + return this._shadowObj.paused; + } + + public get seekable() { + return this._seekableObj; + } + + public get buffered() { + return this._bufferedObj; + } + + public play(): void { + if (!this._initialized) { + this._playing = true; + return; + } + this._obj.play(); + } + + public pause(): void { + if (!this._initialized) { + this._playing = false; + return; + } + this._obj.pause(); + } + + _dispatchEvent(eventName) { + var event = document.createEvent('CustomEvent'); + event.initCustomEvent(eventName, false, false, null); + this._obj.parentElement.dispatchEvent(event); + } + + _pushProperty(propName) { + if (this._initialized) { + this._obj.setProperty(propName, this._shadowObj[propName]); + } + } + + _syncProperties(props) { + for (var i in props) { + this._shadowObj[i] = props[i]; + } + } + + _init() { + this._initialized = true; + if (this._shadowObj.src !== undefined) { + this._pushProperty('src'); + } + if (this._shadowObj.volume !== undefined) { + this._pushProperty('volume'); + } + if (this._playing) { + this._obj.play(); + } + } + + _setSize(width: number, height: number) { + if (!this._obj.width) { + this._obj.width = width; + } + if (!this._obj.height) { + this._obj.height = height; + } + } + + static __dispatchEvent(id: string, eventName: string) { + var target = document.getElementById(id); + target._proxy._dispatchEvent(eventName); + } + + static __syncProperties(id: string, props: any) { + var target = document.getElementById(id); + target._proxy._syncProperties(props); + } + + static __init(id: string) { + var target = document.getElementById(id); + target._proxy._init(); + } + + static __setSize(id: string, width: number, height: number) { + var target = document.getElementById(id); + target._proxy._setSize(width, height); + } + + public static get hiddenLayer() { + return getHiddenVP6PlayersLayer(); + } + } +} + diff --git a/utils/vp6player/org/mozilla/vp6player/VP6Player.as b/utils/vp6player/org/mozilla/vp6player/VP6Player.as new file mode 100644 index 0000000000..b01320666b --- /dev/null +++ b/utils/vp6player/org/mozilla/vp6player/VP6Player.as @@ -0,0 +1,185 @@ +package org.mozilla.vp6player { + import flash.display.MovieClip; + import flash.display.LoaderInfo; + import flash.external.ExternalInterface; + import flash.media.Video; + import flash.net.NetConnection; + import flash.net.NetStream; + import flash.events.NetStatusEvent; + import flash.events.SecurityErrorEvent; + import flash.events.AsyncErrorEvent; + import flash.system.Security; + + + public class VP6Player extends MovieClip { + private var _video:Video; + + private var _connection:NetConnection; + private var _stream:NetStream; + private var _mediaUrl:String; + private var _duration:Number; + private var _volume:Number; + private var _id:String; + + public function VP6Player() { + _video = new Video(); + addChild(_video); + + var params:Object = loaderInfo.parameters; + _id = params['id']; + initExternalMethods(); + + ExternalInterface.call('Shumway.GFX.VP6Player.__init', _id); + _volume = 1.0; + syncProperties({paused: true, volume: _volume}); + } + + private function initExternalMethods():void { + Security.allowDomain('*'); // Flash has file:/// origin -- allowing resource:// access us. + ExternalInterface.addCallback('setProperty', this._setProperty); + ExternalInterface.addCallback('play', this._play); + ExternalInterface.addCallback('pause', this._pause); + ExternalInterface.addCallback('getTime', this._getTime); + ExternalInterface.addCallback('setTime', this._setTime); + ExternalInterface.addCallback('getBufferedTime', this._getBufferedTime); + } + + private function _setProperty(propertyName:String, value:*):void { + switch (propertyName) { + case 'src': + if (this._mediaUrl != value) { + this._mediaUrl = value; + this._connection = null; + } + break; + case 'volume': + _volume = value; + if (!_stream) { + break; + } + var soundTransform = _stream.soundTransform; + soundTransform.volume = value; + _stream.soundTransform = soundTransform; + break; + } + } + + private function _getTime():Number { + return _stream.time; + } + + private function _setTime(offset:Number):void { + _stream.seek(offset); + } + + private function _getBufferedTime():Number { + return _stream.bytesLoaded / _stream.bytesTotal * _duration; + } + + private function netStatusHandler(event:NetStatusEvent):void { + switch (event.info.code) { + case "NetConnection.Connect.Success": + connectStream(); + break; + case "NetStream.Play.Start": + syncProperties({paused: false}); + dispatchPlayerEvent('loadeddata'); + break; + case "NetStream.Play.Stop": + dispatchPlayerEvent('ended'); + break; + case "NetStream.Buffer.Full": + dispatchPlayerEvent('canplay'); + break; + case "NetStream.Buffer.Empty": + //TODO + break; + case "NetStream.Pause.Notify": + syncProperties({paused: true}); + dispatchPlayerEvent('pause'); + break; + case "NetStream.Unpause.Notify": + syncProperties({paused: false}); + dispatchPlayerEvent('play'); + break; + case "NetStream.Seek.Notify": + dispatchPlayerEvent('seeking'); + break; + case "NetStream.Seek.Complete": + dispatchPlayerEvent('seeked'); + break; + case "NetStream.Play.NoSupportedTrackFound": + syncProperties({errorCode: 4}); + dispatchPlayerEvent('error'); + break; + case "NetStream.Play.FileStructureInvalid": + syncProperties({errorCode: 3}); + dispatchPlayerEvent('error'); + break; + case "NetStream.Play.StreamNotFound": + syncProperties({errorCode: 2}); + dispatchPlayerEvent('error'); + break; + } + } + + private function securityErrorHandler(event:SecurityErrorEvent):void { + trace("securityErrorHandler: " + event); + } + + private function asyncErrorHandler(event:AsyncErrorEvent):void { + // ignore AsyncErrorEvent events. + } + + private function connectStream() { + _stream = new NetStream(_connection); + _stream.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler); + _stream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler); + _stream.client = { + onMetaData: this.onMetaData + }; + _duration = NaN; + var soundTransform = _stream.soundTransform; + soundTransform.volume = _volume; + _stream.soundTransform = soundTransform; + + _video.attachNetStream(_stream); + _stream.play(_mediaUrl); + } + + private function onMetaData(metadata:*) { + syncProperties({ + videoWidth: metadata.width, + videoHeight: metadata.height, + duration: metadata.duration + }); + _duration = metadata.duration; + ExternalInterface.call('Shumway.GFX.VP6Player.__setSize', _id, metadata.width, metadata.height); + dispatchPlayerEvent('loadedmetadata'); + } + + private function _play():void { + if (!_connection) { + _connection = new NetConnection(); + _connection.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler); + _connection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler); + _connection.connect(null); + return; + } + + _stream.resume(); + } + + private function _pause():void { + _stream.pause(); + } + + private function syncProperties(props:*):void { + ExternalInterface.call('Shumway.GFX.VP6Player.__syncProperties', _id, props); + } + + private function dispatchPlayerEvent(eventName:String):void { + ExternalInterface.call('Shumway.GFX.VP6Player.__dispatchEvent', _id, eventName); + } + } +} diff --git a/utils/vp6player/vp6player.fla b/utils/vp6player/vp6player.fla new file mode 100644 index 0000000000000000000000000000000000000000..a0a92ff4ece08be9d3def6650e6e6f21a89037ab GIT binary patch literal 4704 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 literal 0 HcmV?d00001 diff --git a/utils/vp6player/vp6player.swf b/utils/vp6player/vp6player.swf new file mode 100755 index 0000000000000000000000000000000000000000..156e0dad76dad446c9be2abcd179bf32912df377 GIT binary patch literal 2537 zcmV1j{9>bkow+I2TMkaN;(x@mtzmSXP!kVHM4 z=A=CK;NEZU+?o0A15gVkK1b*agsx*$>bs5*`q=;`iE7!`^p6>Y=ZG7N2LnOUV*HPLFM zpPPn4EeqU>TDn-BO;4nE+Nvg}XQt*Ba~D^3`JyHGzceZ2XC{Q1iOI>W{B$9oF9`FX z76hSdt!L>iU8lAvi?-Occy>a_ZwYgSnfZb+)xG$ft-sumRpsz|Ek3hMFXk>V`Te++ zrBiBKZBujOB}+M~(I(Yx3+TK>I>O{3|a zr6rmcW%dt8PZls^cb9^yTehf6bgi@mtFx*q7nY}{S4*YR>`Y0RFVEy5)n`^#rluy# zv-7k0xzhY%?$Xi|Z?Yk^8Ogpk8N3No%YrbunqQd{1mf#e8vfu2|0J%O~Et zmY#SsOm$a<{4V`)uUEK8yNPz5r!(#@Z?!!Azv#HM^u*iR736+TZ)@G;dQj#6q2skK zu1x8i-vpP^s2G6O`1heWj2KlVs?&5HfWcS~1K`J_EPEWH(4L{nT-7#JeV5y4H+QI+ za4RTF_gLX2H>h2;MZ0a4nciQeQrlGR!?I}_rVI5hLnD>W^dTtSY}5Em)ig1?*P3l< z;vqGo??0b??n*tLds(&EOmyAYQ)y9CTeq)VS2$hA)%H}G8gXxVlgg?Xe&0oElA-HV zVl+g(S)JNFaPZ(bI|Ih&jFowVu|-v<=I!%FYdRcBglO$i&v4{BoZRfr(Sm!|l7D?_CjCi2czq{P zbazlhiQcR>+Rc6OF#BZn!|Z8-WotDS^N=KJ8s{=Bb6!696=5A(Hf=^uN&w%FgQc-w zGMaFocvot~8SOB)BT6qr?9gG?9q|)}`1#U%AzhNt=XH6@6m^Tex`LJ|g>1k>7Nsxa zJBK#4-eX~uW3Ieq*rMhIR%Wm6Fx5dKlBlyS30uU9nmwr;r7XXShD^h*6jC7-LK5h= z&J{}57qiv2B*C}J;Az2R8B1A6XVV_I1RAZOGt9eFcD1c(sY__N*|HD2;OC5)?1o{( z7sT~5cw29EO>{Z0!gYM~+6A;|G+P=Z_Sl8vZ5Y*dt7VurW#mdPR~gNBFBq+=8U?*A z*=>`q>3gE4%C|1vlgY5T>mh7|Gn%@pqzmcTMtQ4N-CEw-uJVu#Zic?6LjFMF$Cs-g zZ4_(e&CSXtCuu571vkWvUeUHisd2hz%m%@s`_$Flg7Z4dPztBQsc0&Gy(f%Py{Y~%x)zO04EGFYhjYWiaDI4lcq%oWDg=@o zjs|1#aH1#K%lGv^Avi#Akl+vra3nxTAVPvM5=sy*NeEB4J`x!q+#rbz5iUizYlOQ_ zxEq8UCfo?&ZW8Vm;oczJZNjAqcZYDJgu6?)F~Yq`xN*YWBls zjtss-@I!(h5nPTgV1nTkPYGTl_!+^Q(JCNyf_Dg(2$l(^1SL)p_?$0QPo zh7lf$A{0aL<&Z$|v5-W);2S;{z+oR7`k6X_QD6|G;1EWk6h`4|7;*3y8M%Q`WEi99 z2u87+7{za4lz0Q9p4%8D)0nx!JSHIwt}u?qf{yS6pl~1-Uv&g$4LL7j&tj+%eR0Q8 zqLn+iKC0aPeC%uID3ej%Z1g*o0aPE)%sNVc#VK(+70(+c2l}C=Y(|%~!9)42WNe5BSGm z#J_{kkrc|T?u3RR8e@nbMkur3YnV^KVkQOn02?ErkuN))z;8O8KX*DH!C^uet`-5J z!2iTkgu($JF6Qb-l`OkF7XXWX&mzYL;ZKgj{}F6S5K7^9Ksf(bcHqhg;(rB!0{ZT4 zV_?q7yjw|Q{?NtVW1{Yg>r9kDUU?|Vvs|2ZfhcuJ7B zLHW>ALi{c$A9+fc{}_~yJ%!`fLHV(#5Eiq{Pdp{UPl58nQ=%yv5Ox)F{9 zA)*OKcrld`kN!}ZVlj`H-c7$>mHH0+XT94YT)K0Q(v zGun~za68E20YZa^8fiii!Zb`)?uDF;4pD-&308dtmVoiT`%nol63-H-5eK#Vl<|64 zfvUXWDe1~1{Dd9jF#-=gEXkv83RjjHkPN|bl2Fj&8I$c Date: Mon, 1 Jun 2015 15:14:00 -0500 Subject: [PATCH 2/4] Adds generateRandomUUID. --- src/base/utilities.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/base/utilities.ts b/src/base/utilities.ts index 093e4f1572..5d7f35c287 100644 --- a/src/base/utilities.ts +++ b/src/base/utilities.ts @@ -1471,6 +1471,32 @@ module Shumway { return Random.next(); }; + function formatUint16(n): string { + var s = n.toString(16); + switch (n.length) { + case 1: + return '000' + s; + case 2: + return '00' + s; + case 3: + return '0' + s; + default: + return s; + } + } + + export function generateRandomUUID(): string { + var data = new Uint16Array(8); + (window).crypto.getRandomValues(data); + // See rfc 4122, section 4.4 + data[3] = (data[3] & 0x0FFF) | 0x4000; // time_hi_and_version bits 12-15 set to 0100 + data[4] = (data[4] & 0x3FFF) | 0x8000; // clock_seq_hi_and_reserved bits 6-7 to 10 + return formatUint16(data[0]) + formatUint16(data[1]) + '-' + + formatUint16(data[2]) + '-' + formatUint16(data[3]) + '-' + + formatUint16(data[4]) + '-' + formatUint16(data[5]) + + formatUint16(data[6]) + formatUint16(data[7]); + } + function polyfillWeakMap() { if (typeof jsGlobal.WeakMap === 'function') { return; // weak map is supported From 70b79d1ea42714638179dd77381d8600eed447b7 Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Tue, 2 Jun 2015 16:33:46 -0500 Subject: [PATCH 3/4] Delays creation of the video tag --- src/flash/net/NetStream.ts | 60 +++++++++++++++--------------- src/gfx/renderables/renderables.ts | 9 ++++- 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/src/flash/net/NetStream.ts b/src/flash/net/NetStream.ts index bfaab4f9ab..08741c3bbd 100644 --- a/src/flash/net/NetStream.ts +++ b/src/flash/net/NetStream.ts @@ -162,10 +162,6 @@ module Shumway.AVMX.AS.flash.net { notImplemented("public flash.net.NetStream::dispose"); return; } - _getVideoStreamURL(): string { - return this._videoStream.url; - } - play(url: string): void { flash.media.SoundMixer._registerSoundSource(this); @@ -183,8 +179,13 @@ module Shumway.AVMX.AS.flash.net { this._videoStream.play(url, this.checkPolicyFile); } - this._notifyVideoControl(VideoControlEvent.Init, { - url: this._videoStream.url + this._videoStream.urlPromise.then((url: string) => { + this._notifyVideoControl(VideoControlEvent.Init, { + url: url + }); + }, (reason: any) => { + this.dispatchEvent(new this.sec.flash.events.NetStatusEvent(events.NetStatusEvent.NET_STATUS, + false, false, this.sec.createObjectFromJS({code: "NetStream.Play.NoSupportedTrackFound", level: "error"}))); }); } play2(param: flash.net.NetStreamPlayOptions): void { @@ -588,11 +589,10 @@ module Shumway.AVMX.AS.flash.net { private _started: boolean; private _buffer: string; private _bufferTime: number; - private _url: string; + private _urlPromise: PromiseWrapper; private _contentTypeHint: string; private _state: VideoStreamState; private _mediaSource; - private _mediaSourceBuffer; private _mediaSourceBufferLock: Promise; private _head: Uint8Array; private _decoder: IDataDecoder; @@ -617,9 +617,8 @@ module Shumway.AVMX.AS.flash.net { this._started = false; this._buffer = 'empty'; this._bufferTime = 0.1; - this._url = null; + this._urlPromise = new PromiseWrapper(); this._mediaSource = null; - this._mediaSourceBuffer = null; this._mediaSourceBufferLock = null; this._contentTypeHint = null; this._state = VideoStreamState.CLOSED; @@ -627,8 +626,8 @@ module Shumway.AVMX.AS.flash.net { this._netStream = netStream; } - get url(): string { - return this._url; + get urlPromise(): Promise { + return this._urlPromise.promise; } play(url: string, checkPolicyFile: boolean) { @@ -652,10 +651,7 @@ module Shumway.AVMX.AS.flash.net { } else if (flvMode === 'flash') { useVP6Player = true; } else { - setTimeout(() => { - this._netStream.dispatchEvent(new this.sec.flash.events.NetStatusEvent(events.NetStatusEvent.NET_STATUS, - false, false, this.sec.createObjectFromJS({code: "NetStream.Play.NoSupportedTrackFound", level: "error"}))); - }); + this._urlPromise.reject('Not supported'); return; } } @@ -663,10 +659,11 @@ module Shumway.AVMX.AS.flash.net { if (!forceMediaSource && !isMediaSourceEnabled) { somewhatImplemented("public flash.net.NetStream::play"); this._state = VideoStreamState.OPENED; - this._url = FileLoadingService.instance.resolveUrl(url); + url = FileLoadingService.instance.resolveUrl(url); if (useVP6Player) { - this._url = 'vp6:' + this._url; + url = 'vp6:' + url; } + this._urlPromise.resolve(url); return; } @@ -751,21 +748,29 @@ module Shumway.AVMX.AS.flash.net { openInDataGenerationMode() { release || assert(this._state === VideoStreamState.CLOSED); this._state = VideoStreamState.OPENED_DATA_GENERATION; + } + + private _createMediaSource(contentType: string): Promise { + var mediaSourceReady = new PromiseWrapper(); var mediaSource = new MediaSource(); mediaSource.addEventListener('sourceopen', function(e) { this._ensurePlaying(); + var mediaSourceBuffer = this._mediaSource.addSourceBuffer(contentType); + mediaSourceReady.resolve(mediaSourceBuffer); }.bind(this)); mediaSource.addEventListener('sourceend', function(e) { this._mediaSource = null; }.bind(this)); this._mediaSource = mediaSource; - this._url = URL.createObjectURL(mediaSource); + this._mediaSourceBufferLock = mediaSourceReady.promise; + var url = URL.createObjectURL(mediaSource); + this._urlPromise.resolve(url); + return mediaSourceReady.promise; } appendBytes(bytes: Uint8Array) { release || assert(this._state === VideoStreamState.OPENED_DATA_GENERATION || this._state === VideoStreamState.OPENED); - release || assert(this._mediaSource); if (this._decoder) { this._decoder.push(bytes); @@ -793,21 +798,19 @@ module Shumway.AVMX.AS.flash.net { // FLV data needs to be parsed and wrapped with MP4 tags. var flvDecoder = new FlvMp4Decoder(this.sec); flvDecoder.onHeader = function (contentType) { - this._mediaSourceBuffer = this._mediaSource.addSourceBuffer(contentType); - this._mediaSourceBufferLock = Promise.resolve(undefined); + this._createMediaSource(contentType); }.bind(this); flvDecoder.onData = this._queueData.bind(this); this._decoder = flvDecoder; } else if (contentType) { - // Let's use identity decoder for reset of the types. + // Let's use identity decoder for rest of the types. this._decoder = { onData: this._queueData.bind(this), onError: function (e) { /* */ }, push: function (bytes: Uint8Array) { this.onData(bytes); }, close: function () { /* */ } }; - this._mediaSourceBuffer = this._mediaSource.addSourceBuffer(contentType); - this._mediaSourceBufferLock = Promise.resolve(undefined); + this._createMediaSource(contentType); } } @@ -829,13 +832,12 @@ module Shumway.AVMX.AS.flash.net { private _queueData(bytes: Uint8Array): void { // We need to chain all appendBuffer operations using 'update' event. - var buffer = this._mediaSourceBuffer; - this._mediaSourceBufferLock = this._mediaSourceBufferLock.then(function () { + this._mediaSourceBufferLock = this._mediaSourceBufferLock.then(function (buffer) { buffer.appendBuffer(bytes); return new Promise(function (resolve) { buffer.addEventListener('update', function updateHandler() { buffer.removeEventListener('update', updateHandler); - resolve(); + resolve(buffer); }); }); }); @@ -852,7 +854,7 @@ module Shumway.AVMX.AS.flash.net { throw new Error('Internal appendBytes error'); } this._decoder.close(); - this._mediaSourceBufferLock.then(function () { + this._mediaSourceBufferLock.then(function (buffer) { if (this._mediaSource) { this._mediaSource.endOfStream(); } diff --git a/src/gfx/renderables/renderables.ts b/src/gfx/renderables/renderables.ts index 1684bae677..c137c8e6aa 100644 --- a/src/gfx/renderables/renderables.ts +++ b/src/gfx/renderables/renderables.ts @@ -416,6 +416,9 @@ module Shumway.GFX { } public checkForUpdate() { + if (!this._video) { + return; + } if (this._lastTimeInvalidated !== this._video.currentTime) { // Videos composited using DOM elements don't need to invalidate parents. if (!this._isDOMElement) { @@ -433,11 +436,13 @@ module Shumway.GFX { if (renderable.willRender()) { // If the nodes video element isn't already on the video layer, mark the node as invalid to // make sure the video element will be added the next time the renderer reaches it. - if (!renderable._video.parentElement || + if (!renderable._video || !renderable._video.parentElement || renderable._video.parentElement === VP6Player.hiddenLayer) { renderable.invalidate(); } - renderable._video.style.zIndex = renderable.parents[0].depth + ''; + if (renderable._video) { + renderable._video.style.zIndex = renderable.parents[0].depth + ''; + } } else if (renderable._video.parentElement) { // The nodes video element should be removed if no longer visible. renderable._dispatchEvent(NodeEventType.RemovedFromStage); From 6a3fb3f765a5e0d477a80c8b68dbf28d81bba402 Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Wed, 3 Jun 2015 10:53:40 -0500 Subject: [PATCH 4/4] Sending flvstream data --- src/base/remoting.ts | 1 + src/flash/net/NetStream.ts | 36 ++++++-- src/flash/settings.ts | 4 + src/gfx/easelHost.ts | 4 + src/gfx/vp6player.ts | 42 ++++++++- src/gfx/window/windowEaselHost.ts | 2 + src/player/player.ts | 8 ++ src/player/window/windowPlayer.ts | 8 ++ src/shell/shell.ts | 4 + .../org/mozilla/vp6player/VP6Player.as | 84 ++++++++++++++++-- utils/vp6player/vp6player.swf | Bin 2537 -> 3165 bytes 11 files changed, 179 insertions(+), 14 deletions(-) diff --git a/src/base/remoting.ts b/src/base/remoting.ts index 3c95a95798..ab45831494 100644 --- a/src/base/remoting.ts +++ b/src/base/remoting.ts @@ -219,6 +219,7 @@ module Shumway.Remoting { registerImage(syncId: number, symbolId: number, imageType: ImageType, data: Uint8Array, alphaData: Uint8Array): Promise; fscommand(command: string, args: string): void; + sendVP6StreamData(url: string, data: Uint8Array): void; } /** diff --git a/src/flash/net/NetStream.ts b/src/flash/net/NetStream.ts index 08741c3bbd..e2e39c8088 100644 --- a/src/flash/net/NetStream.ts +++ b/src/flash/net/NetStream.ts @@ -641,7 +641,7 @@ module Shumway.AVMX.AS.flash.net { var flvMode: string = flvOption.value; var forceMediaSource = false; var useVP6Player = false; - if (flvMode === 'alwaysflash') { + if (vp6PlayerOption.value) { useVP6Player = true; } else if (/\.flv($|\?)/i.test(url)) { if (flvMode === 'supported') { @@ -768,6 +768,20 @@ module Shumway.AVMX.AS.flash.net { return mediaSourceReady.promise; } + private _createVP6PlayerDecoder(): IDataDecoder { + var url = 'vp6:flvstream:' + generateRandomUUID(); + this._urlPromise.resolve(url); + var player: any = this.sec.player; + // Shortcut: using decoder interface to push data to the gfx iframe. + // FIXME don't use player._gfxService -- implement interface + return { + onData: null, + onError: null, + push: function (bytes: Uint8Array) { player.sendVP6StreamData(url, bytes); }, + close: function () { player.sendVP6StreamData(url, null /* EOF */); } + }; + } + appendBytes(bytes: Uint8Array) { release || assert(this._state === VideoStreamState.OPENED_DATA_GENERATION || this._state === VideoStreamState.OPENED); @@ -777,6 +791,12 @@ module Shumway.AVMX.AS.flash.net { return; } + if (vp6PlayerOption.value) { + this._decoder = this._createVP6PlayerDecoder(); + this._decoder.push(bytes); + return; + } + // First we need to parse some content to find out mime type and codecs // for MediaSource. Caching some data at the beginning until we can tell // the type of the content. @@ -854,12 +874,14 @@ module Shumway.AVMX.AS.flash.net { throw new Error('Internal appendBytes error'); } this._decoder.close(); - this._mediaSourceBufferLock.then(function (buffer) { - if (this._mediaSource) { - this._mediaSource.endOfStream(); - } - this.close(); - }.bind(this)); + if (this._mediaSourceBufferLock) { + this._mediaSourceBufferLock.then(function (buffer) { + if (this._mediaSource) { + this._mediaSource.endOfStream(); + } + this.close(); + }.bind(this)); + } } somewhatImplemented("public flash.net.NetStream::appendBytesAction"); } diff --git a/src/flash/settings.ts b/src/flash/settings.ts index 83007554d7..a54c407d9b 100644 --- a/src/flash/settings.ts +++ b/src/flash/settings.ts @@ -30,6 +30,10 @@ module Shumway.AVMX.AS { new Shumway.Options.Option(null, "Use Media Source for Video", "boolean", false, "Enables Media Source Extension API for NetStream.") ); + export var vp6PlayerOption = flashOptions.register ( + new Shumway.Options.Option(null, "Use VP6Player for Video", "boolean", false, "Always use VP6Player for media playback.") + ); + export var mediaSourceMP3Option = flashOptions.register ( new Shumway.Options.Option(null, "Use Media Source for MP3", "boolean", true, "Enables Media Source Extension API for MP3 streams.") ); diff --git a/src/gfx/easelHost.ts b/src/gfx/easelHost.ts index 14fb3f5d7f..a094ea9ead 100644 --- a/src/gfx/easelHost.ts +++ b/src/gfx/easelHost.ts @@ -185,6 +185,10 @@ module Shumway.GFX { arguments.length; // keeping from closure removal } + processVP6StreamData(url: string, data: Uint8Array) { + VP6Player.addVP6StreamData(url, data); + } + processFrame() { arguments.length; // keeping from closure removal } diff --git a/src/gfx/vp6player.ts b/src/gfx/vp6player.ts index 04707cfc49..b29b574965 100644 --- a/src/gfx/vp6player.ts +++ b/src/gfx/vp6player.ts @@ -31,6 +31,22 @@ module Shumway.GFX { return hiddenVP6PlayersLayer; } + function packageAndSendVP6Data(obj: HTMLObjectElement, data: Uint8Array): void { + if (data == null) { + (obj).addVP6Data(null); + return; + } + var maxSize = 1023; // divisible by 3 + var pos = 0; + while (pos + maxSize < data.length) { + var chunk = btoa(String.fromCharCode.apply(null, data.subarray(pos, pos + maxSize))); + (obj).addVP6Data(chunk); + pos += maxSize; + } + var chunk = btoa(String.fromCharCode.apply(null, data.subarray(pos))); + (obj).addVP6Data(chunk); + } + export class VP6Player { public static SWFPath: string = 'resource://shumway/vp6player.swf'; @@ -41,6 +57,7 @@ module Shumway.GFX { private _errorObj: {code: number}; private _seekableObj: {length: number}; private _bufferedObj: {length: number; end: (index: number) => number}; + private _vp6Data: Uint8Array[]; public constructor() { var id = generateUniqueID(); @@ -101,7 +118,7 @@ module Shumway.GFX { var propertiesToTransfer = ['width', 'height', 'src', 'currentTime', 'volume', 'error', 'videoWidth', 'videoHeight', 'paused', 'seekable', 'buffered', 'play', 'pause', '_dispatchEvent', '_pushProperty', - '_syncProperties', '_init', '_setSize']; + '_syncProperties', '_init', '_setSize', '_addVP6StreamData']; propertiesToTransfer.forEach((x) => { var desc = Object.getOwnPropertyDescriptor(VP6Player.prototype, x); Debug.assert(desc); @@ -133,6 +150,7 @@ module Shumway.GFX { } public set src(value: string) { + this._obj.setAttribute('data-src', 'vp6:' + value); this._shadowObj.src = value; this._pushProperty('src'); } @@ -227,6 +245,12 @@ module Shumway.GFX { if (this._shadowObj.volume !== undefined) { this._pushProperty('volume'); } + if (this._vp6Data) { + this._vp6Data.forEach((data) => { + packageAndSendVP6Data(this._obj, data); + }); + this._vp6Data = null; + } if (this._playing) { this._obj.play(); } @@ -241,6 +265,17 @@ module Shumway.GFX { } } + _addVP6StreamData(data: Uint8Array) { + if (this._initialized) { + packageAndSendVP6Data(this._obj, data); + } else { + if (!this._vp6Data) { + this._vp6Data = []; + } + this._vp6Data.push(data); + } + } + static __dispatchEvent(id: string, eventName: string) { var target = document.getElementById(id); target._proxy._dispatchEvent(eventName); @@ -264,6 +299,11 @@ module Shumway.GFX { public static get hiddenLayer() { return getHiddenVP6PlayersLayer(); } + + public static addVP6StreamData(url: string, data: Uint8Array) { + var target = document.querySelector('object[data-src="' + url + '"]'); + target._proxy._addVP6StreamData(data); + } } } diff --git a/src/gfx/window/windowEaselHost.ts b/src/gfx/window/windowEaselHost.ts index d48f115319..1a3e71d641 100644 --- a/src/gfx/window/windowEaselHost.ts +++ b/src/gfx/window/windowEaselHost.ts @@ -103,6 +103,8 @@ module Shumway.GFX.Window { this._sendRegisterImageResponse.bind(this, data.requestId)); } else if (data.type === 'fscommand') { this.processFSCommand(data.command, data.args); + } else if (data.type === 'vp6data') { + this.processVP6StreamData(data.url, data.data); } else { // release || Debug.assertUnreachable("Unhandled remoting event " + data.type); } diff --git a/src/player/player.ts b/src/player/player.ts index 6f41260230..56254d0c5c 100644 --- a/src/player/player.ts +++ b/src/player/player.ts @@ -107,6 +107,10 @@ module Shumway.Player { throw new Error('This method is abstract'); } + sendVP6StreamData(url: string, data: Uint8Array): void { + throw new Error('This method is abstract'); + } + public processUpdates(updates: DataBuffer, assets: any []) { var deserializer = new Remoting.Player.PlayerChannelDeserializer( this.sec, updates, assets); @@ -499,6 +503,10 @@ module Shumway.Player { this._gfxService.fscommand(command, args); } + public sendVP6StreamData(url: string, data: Uint8Array): void { + this._gfxService.sendVP6StreamData(url, data); + } + public requestRendering(): void { this._pumpDisplayListUpdates(); } diff --git a/src/player/window/windowPlayer.ts b/src/player/window/windowPlayer.ts index 22f0cf7b6c..13a31928bf 100644 --- a/src/player/window/windowPlayer.ts +++ b/src/player/window/windowPlayer.ts @@ -117,6 +117,14 @@ module Shumway.Player.Window { }); } + sendVP6StreamData(url: string, data: Uint8Array): void { + this._peer.postAsyncMessage({ + type: 'vp6data', + url: url, + data: data + }); + } + private onWindowMessage(data) { if (typeof data === 'object' && data !== null) { switch (data.type) { diff --git a/src/shell/shell.ts b/src/shell/shell.ts index 6dd8baa2d0..0a350818a9 100644 --- a/src/shell/shell.ts +++ b/src/shell/shell.ts @@ -178,6 +178,10 @@ module Shumway.Shell { microTaskQueue.stop(); } } + + sendVP6StreamData(url: string, data: Uint8Array): void { + // console.log('vp6PlayerData'); + } } export var verbose = false; diff --git a/utils/vp6player/org/mozilla/vp6player/VP6Player.as b/utils/vp6player/org/mozilla/vp6player/VP6Player.as index b01320666b..c219c6bf83 100644 --- a/utils/vp6player/org/mozilla/vp6player/VP6Player.as +++ b/utils/vp6player/org/mozilla/vp6player/VP6Player.as @@ -5,13 +5,14 @@ import flash.media.Video; import flash.net.NetConnection; import flash.net.NetStream; + import flash.net.NetStreamAppendBytesAction; import flash.events.NetStatusEvent; import flash.events.SecurityErrorEvent; import flash.events.AsyncErrorEvent; import flash.system.Security; + import flash.utils.ByteArray; - - public class VP6Player extends MovieClip { + public class VP6Player extends MovieClip { private var _video:Video; private var _connection:NetConnection; @@ -20,8 +21,9 @@ private var _duration:Number; private var _volume:Number; private var _id:String; + private var _buffer:ByteArray; - public function VP6Player() { + public function VP6Player() { _video = new Video(); addChild(_video); @@ -32,7 +34,7 @@ ExternalInterface.call('Shumway.GFX.VP6Player.__init', _id); _volume = 1.0; syncProperties({paused: true, volume: _volume}); - } + } private function initExternalMethods():void { Security.allowDomain('*'); // Flash has file:/// origin -- allowing resource:// access us. @@ -42,6 +44,8 @@ ExternalInterface.addCallback('getTime', this._getTime); ExternalInterface.addCallback('setTime', this._setTime); ExternalInterface.addCallback('getBufferedTime', this._getBufferedTime); + ExternalInterface.addCallback('getBufferedTime', this._getBufferedTime); + ExternalInterface.addCallback('addVP6Data', this._addVP6Data); } private function _setProperty(propertyName:String, value:*):void { @@ -76,6 +80,61 @@ return _stream.bytesLoaded / _stream.bytesTotal * _duration; } + private static const base64DecodeMap:Array = [ // starts at 0x2B + 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, + 0, 0, 0, 0, 0, 0, 0, // 0x3A-0x40 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, // 0x5B-0x0x60 + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51]; + private static const base64DecodeMapOffset:int = 0x2B; + private static const base64EOF:int = 0x3D; + + private static function _decodeBase64(encoded:String, decoded:ByteArray):void { + var ch:int, code:int, code2:int; + var i:int = 0; + while (i < encoded.length) { + ch = encoded.charCodeAt(i++); + code = base64DecodeMap[ch - base64DecodeMapOffset]; + ch = encoded.charCodeAt(i++); + code2 = base64DecodeMap[ch - base64DecodeMapOffset]; + decoded.writeByte((code << 2) | ((code2 & 0x30) >> 4)); + + ch = encoded.charCodeAt(i++); + if (ch == base64EOF) { + break; + } + code = base64DecodeMap[ch - base64DecodeMapOffset]; + decoded.writeByte(((code2 & 0x0f) << 4) | ((code & 0x3c) >> 2)); + + ch = encoded.charCodeAt(i++); + if (ch == base64EOF) { + break; + } + code2 = base64DecodeMap[ch - base64DecodeMapOffset]; + decoded.writeByte(((code & 0x03) << 6) | code2); + } + } + + private function _addVP6Data(data:String):void { + if (data === null) { + if (_stream) { + _stream.appendBytesAction(NetStreamAppendBytesAction.END_SEQUENCE); + } + // TODO cache for !_stream + return; + } + var decoded:ByteArray = _buffer || new ByteArray(); + _decodeBase64(data, decoded); + if (_stream) { + decoded.position = 0; + _stream.appendBytes(decoded); + _buffer = null; + } else { + _buffer = decoded; + } + } + private function netStatusHandler(event:NetStatusEvent):void { switch (event.info.code) { case "NetConnection.Connect.Success": @@ -144,7 +203,20 @@ _stream.soundTransform = soundTransform; _video.attachNetStream(_stream); - _stream.play(_mediaUrl); + + var isStream = _mediaUrl.indexOf('flvstream:') == 0; + if (isStream) { + _stream.play(null); + _stream.appendBytesAction(NetStreamAppendBytesAction.RESET_BEGIN); + if (_buffer) { + _buffer.position = 0; + _stream.appendBytes(_buffer); + _buffer = null; + } + } else { + _stream.play(_mediaUrl); + + } } private function onMetaData(metadata:*) { @@ -181,5 +253,5 @@ private function dispatchPlayerEvent(eventName:String):void { ExternalInterface.call('Shumway.GFX.VP6Player.__dispatchEvent', _id, eventName); } - } + } } diff --git a/utils/vp6player/vp6player.swf b/utils/vp6player/vp6player.swf index 156e0dad76dad446c9be2abcd179bf32912df377..83457e8c631aa4572fa4b7aa19edab1564a2f8dc 100755 GIT binary patch literal 3165 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