diff --git a/examples/inspector/js/inspector.js b/examples/inspector/js/inspector.js index fd883816f0..7810c85354 100644 --- a/examples/inspector/js/inspector.js +++ b/examples/inspector/js/inspector.js @@ -186,9 +186,11 @@ function executeFile(file, buffer, movieParams, remoteDebugging) { showMessage("Running in the Interpreter"); } + Shumway.SystemResourcesLoadingService.instance = + new Shumway.Player.BrowserSystemResourcesLoadingService(builtinPath, playerglobalInfo, shellAbcPath); + if (filename.endsWith(".abc")) { - libraryScripts = {}; - Shumway.createAVM2(builtinPath, shellAbcPath, sysMode, appMode, function (avm2) { + Shumway.createAVM2(Shumway.AVM2LoadLibrariesFlags.Builtin | Shumway.AVM2LoadLibrariesFlags.Shell, sysMode, appMode).then(function (avm2) { function runAbc(file, buffer) { avm2.applicationDomain.executeAbc(new Shumway.AVM2.ABC.AbcFile(new Uint8Array(buffer), file)); } @@ -201,7 +203,7 @@ function executeFile(file, buffer, movieParams, remoteDebugging) { } }); } else if (filename.endsWith(".swf")) { - Shumway.createAVM2(builtinPath, playerglobalInfo, sysMode, appMode, function (avm2) { + Shumway.createAVM2(Shumway.AVM2LoadLibrariesFlags.Builtin | Shumway.AVM2LoadLibrariesFlags.Playerglobal, sysMode, appMode).then(function (avm2) { function runSWF(file, buffer) { var swfURL = Shumway.FileLoadingService.instance.resolveUrl(file); @@ -212,7 +214,8 @@ function executeFile(file, buffer, movieParams, remoteDebugging) { easel.stage.invalidate(); }); syncGFXOptions(easel.options); - var player = new Shumway.Player.Test.TestPlayer(); + var gfxService = new Shumway.Player.Test.TestGFXService(); + var player = new Shumway.Player.Player(gfxService); player.movieParams = movieParams; player.stageAlign = state.salign; player.stageScale = state.scale; @@ -246,7 +249,7 @@ function executeFile(file, buffer, movieParams, remoteDebugging) { } }); } else if (filename.endsWith(".js") || filename.endsWith("/")) { - Shumway.createAVM2(builtinPath, playerglobalInfo, sysMode, appMode, function (avm2) { + Shumway.createAVM2(Shumway.AVM2LoadLibrariesFlags.Builtin | Shumway.AVM2LoadLibrariesFlags.Playerglobal, sysMode, appMode).then(function (avm2) { executeUnitTests(file, avm2); }); } diff --git a/examples/inspector/js/inspectorPlayer.js b/examples/inspector/js/inspectorPlayer.js index e6f87bf20b..5a6f6b660b 100644 --- a/examples/inspector/js/inspectorPlayer.js +++ b/examples/inspector/js/inspectorPlayer.js @@ -52,9 +52,12 @@ function runSwfPlayer(data) { Shumway.FileLoadingService.instance = new Shumway.Player.BrowserFileLoadingService(); Shumway.FileLoadingService.instance.init(file, data.fileReadChunkSize); } - Shumway.createAVM2(builtinPath, playerglobalInfo, sysMode, appMode, function (avm2) { + Shumway.SystemResourcesLoadingService.instance = + new Shumway.Player.BrowserSystemResourcesLoadingService(builtinPath, playerglobalInfo); + Shumway.createAVM2(Shumway.AVM2LoadLibrariesFlags.Builtin | Shumway.AVM2LoadLibrariesFlags.Playerglobal, sysMode, appMode).then(function (avm2) { function runSWF(file) { - var player = new Shumway.Player.Window.WindowPlayer(window); + var gfxService = new Shumway.Player.Window.WindowGFXService(window); + var player = new Shumway.Player.Player(gfxService); player.movieParams = movieParams; player.stageAlign = stageAlign; player.stageScale = stageScale; diff --git a/extension/firefox/content/web/viewerPlayer.js b/extension/firefox/content/web/viewerPlayer.js index 08b9d7659a..cd7bd57c5a 100644 --- a/extension/firefox/content/web/viewerPlayer.js +++ b/extension/firefox/content/web/viewerPlayer.js @@ -42,9 +42,12 @@ function runSwfPlayer(flashParams) { Shumway.frameRateOption.value = flashParams.turboMode ? 60 : -1; Shumway.AVM2.Verifier.enabled.value = compilerSettings.verifier; - Shumway.createAVM2(builtinPath, viewerPlayerglobalInfo, sysMode, appMode, function (avm2) { + Shumway.SystemResourcesLoadingService.instance = + new Shumway.Player.BrowserSystemResourcesLoadingService(builtinPath, viewerPlayerglobalInfo); + Shumway.createAVM2(Shumway.AVM2LoadLibrariesFlags.Builtin | Shumway.AVM2LoadLibrariesFlags.Playerglobal, sysMode, appMode).then(function (avm2) { function runSWF(file, buffer, baseUrl) { - var player = new Shumway.Player.Window.WindowPlayer(window, window.parent); + var gfxService = new Shumway.Player.Window.WindowGFXService(window, window.parent); + var player = new Shumway.Player.Player(gfxService); player.defaultStageColor = flashParams.bgcolor; player.movieParams = flashParams.movieParams; player.stageAlign = (objectParams && (objectParams.salign || objectParams.align)) || ''; diff --git a/src/avm2/domain.ts b/src/avm2/domain.ts index 3fbedb143e..fd5402af30 100644 --- a/src/avm2/domain.ts +++ b/src/avm2/domain.ts @@ -103,29 +103,6 @@ module Shumway.AVM2.Runtime { return null; } - function promiseFile(path, responseType) { - return new Promise(function (resolve, reject) { - SWF.enterTimeline('Load file', path); - var xhr = new XMLHttpRequest(); - xhr.open('GET', path); - xhr.responseType = responseType; - xhr.onload = function () { - SWF.leaveTimeline(); - var response = xhr.response; - if (response) { - if (responseType === 'json' && xhr.responseType !== 'json') { - // some browsers (e.g. Safari) have no idea what json is - response = JSON.parse(response); - } - resolve(response); - } else { - reject('Unable to load ' + path + ': ' + xhr.statusText); - } - }; - xhr.send(); - }); - } - enum StackFormat { SpiderMonkey, V8 @@ -197,12 +174,13 @@ module Shumway.AVM2.Runtime { return !!playerglobal; } - public static loadPlayerglobal(abcsPath, catalogPath) { + public static loadPlayerglobal(): Promise { if (playerglobalLoadedPromise) { return Promise.reject('Playerglobal is already loaded'); } - playerglobalLoadedPromise = Promise.all([ - promiseFile(abcsPath, 'arraybuffer'), promiseFile(catalogPath, 'json')]). + return Promise.all([ + SystemResourcesLoadingService.instance.load(SystemResourceId.PlayerglobalAbcs), + SystemResourcesLoadingService.instance.load(SystemResourceId.PlayerglobalManifest)]). then(function (result) { playerglobal = { abcs: result[0], @@ -223,10 +201,7 @@ module Shumway.AVM2.Runtime { } } } - }, function (e) { - console.error(e); }); - return playerglobalLoadedPromise; } } diff --git a/src/base/remoting.ts b/src/base/remoting.ts index eba1fe9f17..2da8d6babe 100644 --- a/src/base/remoting.ts +++ b/src/base/remoting.ts @@ -197,4 +197,25 @@ module Shumway.Remoting { screenWidth: number; screenHeight: number; } + + export interface IGFXServiceObserver { + displayParameters(displayParameters: DisplayParameters); + focusEvent(data: any); + keyboardEvent(data: any); + mouseEvent(data: any); + videoEvent(id: number, eventType: VideoPlaybackEvent, data: any); + } + + export interface IGFXService { + addObserver(observer: IGFXServiceObserver); + removeObserver(observer: IGFXServiceObserver); + + update(updates: DataBuffer, assets: Array): void; + updateAndGet(updates: DataBuffer, assets: Array): any; + frame(): void; + videoControl(id: number, eventType: VideoControlEvent, data: any): any; + registerFont(syncId: number, data: any): Promise; + registerImage(syncId: number, symbolId: number, data: any): Promise; + fscommand(command: string, args: string): void; + } } diff --git a/src/base/utilities.ts b/src/base/utilities.ts index 4b61bc3877..b4cfd6d9e6 100644 --- a/src/base/utilities.ts +++ b/src/base/utilities.ts @@ -3421,6 +3421,21 @@ module Shumway { export var instance: IFileLoadingService; } + export enum SystemResourceId { + BuiltinAbc = 0, + PlayerglobalAbcs = 1, + PlayerglobalManifest = 2, + ShellAbc = 3 + } + + export interface ISystemResourcesLoadingService { + load(id: SystemResourceId): Promise; + } + + export module SystemResourcesLoadingService { + export var instance: ISystemResourcesLoadingService; + } + export function registerCSSFont(id: number, buffer: ArrayBuffer, forceFontInit: boolean) { if (!inBrowser) { Debug.warning('Cannot register CSS font outside the browser'); diff --git a/src/flash/display/Loader.ts b/src/flash/display/Loader.ts index 443e18e3a5..0e8290488d 100644 --- a/src/flash/display/Loader.ts +++ b/src/flash/display/Loader.ts @@ -472,9 +472,8 @@ module Shumway.AVM2.AS.flash.display { var symbol = BitmapSymbol.FromData(data); this._imageSymbol = symbol; var resolver: Timeline.IAssetResolver = AVM2.instance.globals['Shumway.Player.Utils']; - resolver.registerFontOrImage(symbol, data); + resolver.registerImage(symbol, data); release || assert(symbol.resolveAssetPromise); - release || assert(symbol.ready === false); } private _applyDecodedImage(symbol: BitmapSymbol) { diff --git a/src/flash/display/LoaderInfo.ts b/src/flash/display/LoaderInfo.ts index 3174586b46..43f21d9e4a 100644 --- a/src/flash/display/LoaderInfo.ts +++ b/src/flash/display/LoaderInfo.ts @@ -370,12 +370,25 @@ module Shumway.AVM2.AS.flash.display { release || assert(symbol, "Unknown symbol type " + data.type); this._dictionary[id] = symbol; if (symbol.ready === false) { - var resolver: Timeline.IAssetResolver = AVM2.Runtime.AVM2.instance.globals['Shumway.Player.Utils']; - resolver.registerFontOrImage(symbol, data); + this._registerFontOrImage(symbol, data); } return symbol; } + private _registerFontOrImage(symbol: Timeline.EagerlyResolvedSymbol, data: any) { + var resolver: Timeline.IAssetResolver = AVM2.Runtime.AVM2.instance.globals['Shumway.Player.Utils']; + switch (data.type) { + case 'font': + resolver.registerFont(symbol, data); + break; + case 'image': + resolver.registerImage(symbol, data); + break; + default: + throw new Error('Unsupported assert type: ' + data.type); + } + } + getRootSymbol(): flash.display.SpriteSymbol { release || assert(this._file instanceof SWFFile); release || assert(this._file.framesLoaded > 0); diff --git a/src/flash/symbol.ts b/src/flash/symbol.ts index a43b432513..90dae1cc39 100644 --- a/src/flash/symbol.ts +++ b/src/flash/symbol.ts @@ -27,7 +27,8 @@ module Shumway.Timeline { import ActionScriptVersion = flash.display.ActionScriptVersion; export interface IAssetResolver { - registerFontOrImage(symbol: Timeline.EagerlyResolvedSymbol, data: any): void; + registerFont(symbol: Timeline.EagerlyResolvedSymbol, data: any): void; + registerImage(symbol: Timeline.EagerlyResolvedSymbol, data: any): void; } export interface EagerlyResolvedSymbol { diff --git a/src/gfx/test/testEaselHost.ts b/src/gfx/test/testEaselHost.ts index 2e9cb17d3d..65c5322ae7 100644 --- a/src/gfx/test/testEaselHost.ts +++ b/src/gfx/test/testEaselHost.ts @@ -91,6 +91,14 @@ module Shumway.GFX.Test { return Promise.resolve(buffer); } + private _sendRegisterFontOrImageResponse(requestId: number, result: any) { + this._worker.postMessage({ + type: 'registerFontOrImageResponse', + requestId: requestId, + result: result + }); + } + _onWorkerMessage(e, async: boolean = true): any { var data = e.data; if (typeof data !== 'object' || data === null) { @@ -118,7 +126,7 @@ module Shumway.GFX.Test { break; case 'registerFontOrImage': this.processRegisterFontOrImage(data.syncId, data.symbolId, data.assetType, data.data, - data.resolve); + this._sendRegisterFontOrImageResponse.bind(this, data.requestId)); e.handled = true; break; case 'fscommand': diff --git a/src/gfx/window/windowEaselHost.ts b/src/gfx/window/windowEaselHost.ts index 40f1d90c38..b90665ad6d 100644 --- a/src/gfx/window/windowEaselHost.ts +++ b/src/gfx/window/windowEaselHost.ts @@ -80,6 +80,14 @@ module Shumway.GFX.Window { }.bind(this)); } + private _sendRegisterFontOrImageResponse(requestId: number, result: any) { + this._playerWindow.postMessage({ + type: 'registerFontOrImageResponse', + requestId: requestId, + result: result + }, '*'); + } + private onWindowMessage(data, async: boolean = true) { if (typeof data === 'object' && data !== null) { if (data.type === 'player') { @@ -97,7 +105,7 @@ module Shumway.GFX.Window { data.result = this.processVideoControl(data.id, data.eventType, data.data); } else if (data.type === 'registerFontOrImage') { this.processRegisterFontOrImage(data.syncId, data.symbolId, data.assetType, data.data, - data.resolve); + this._sendRegisterFontOrImageResponse.bind(this, data.requestId)); } else if (data.type === 'fscommand') { this.processFSCommand(data.command, data.args); } else if (data.type === 'timelineResponse' && data.timeline) { diff --git a/src/player/avmLoader.ts b/src/player/avmLoader.ts index a2813d8d5e..508ef089bf 100644 --- a/src/player/avmLoader.ts +++ b/src/player/avmLoader.ts @@ -21,19 +21,20 @@ module Shumway { import assert = Shumway.Debug.assert; import ExecutionMode = Shumway.AVM2.Runtime.ExecutionMode; - export interface LibraryPathInfo { - abcs: string; - catalog: string; + export enum AVM2LoadLibrariesFlags { + Builtin = 1, + Playerglobal = 2, + Shell = 4 } - export function createAVM2(builtinPath: string, - libraryPath: any, /* LibraryPathInfo | string */ - sysMode: ExecutionMode, appMode: ExecutionMode, - next: (avm2: AVM2) => void) { + export function createAVM2(libraries: AVM2LoadLibrariesFlags, + sysMode: ExecutionMode, + appMode: ExecutionMode): Promise { var avm2; - release || assert (builtinPath); - SWF.enterTimeline('Load file', builtinPath); - new BinaryFileReader(builtinPath).readAll(null, function (buffer) { + var result = new PromiseWrapper(); + release || assert (!!(libraries & AVM2LoadLibrariesFlags.Builtin)); + SWF.enterTimeline('Load builton.abc file'); + SystemResourcesLoadingService.instance.load(SystemResourceId.BuiltinAbc).then(function (buffer) { SWF.leaveTimeline(); AVM2.initialize(sysMode, appMode); avm2 = AVM2.instance; @@ -47,20 +48,24 @@ module Shumway { // If library is shell.abc, then just go ahead and run it now since // it's not worth doing it lazily given that it is so small. - if (typeof libraryPath === 'string') { - new BinaryFileReader(libraryPath).readAll(null, function (buffer) { - avm2.systemDomain.executeAbc(new AbcFile(new Uint8Array(buffer), libraryPath)); - next(avm2); - }); + if (!!(libraries & AVM2LoadLibrariesFlags.Shell)) { + SystemResourcesLoadingService.instance.load(SystemResourceId.ShellAbc).then(function (buffer) { + avm2.systemDomain.executeAbc(new AbcFile(new Uint8Array(buffer), "shell.abc")); + result.resolve(avm2); + }, result.reject); return; } - var libraryPathInfo: LibraryPathInfo = libraryPath; - if (!AVM2.isPlayerglobalLoaded()) { - AVM2.loadPlayerglobal(libraryPathInfo.abcs, libraryPathInfo.catalog).then(function () { - next(avm2); - }); + if (!!(libraries & AVM2LoadLibrariesFlags.Playerglobal) && + !AVM2.isPlayerglobalLoaded()) { + AVM2.loadPlayerglobal().then(function () { + result.resolve(avm2); + }, result.reject); + return; } - }); + + result.resolve(avm2); + }, result.reject); + return result.promise; } } diff --git a/src/player/external.ts b/src/player/external.ts index 283d663b34..3226bfe88d 100644 --- a/src/player/external.ts +++ b/src/player/external.ts @@ -176,7 +176,56 @@ module Shumway.Player { } navigateTo(url: string, target: string) { - window.parent.open(this.resolveUrl(url), target || '_blank'); + window.open(this.resolveUrl(url), target || '_blank'); + } + } + + export class BrowserSystemResourcesLoadingService implements ISystemResourcesLoadingService { + public constructor(public builtinPath: string, + public viewerPlayerglobalInfo?: {abcs: string; catalog: string;}, + public shellPath?: string) { + } + + public load(id: SystemResourceId): Promise { + switch (id) { + case SystemResourceId.BuiltinAbc: + return this._promiseFile(this.builtinPath, 'arraybuffer'); + case SystemResourceId.PlayerglobalAbcs: + return this._promiseFile(this.viewerPlayerglobalInfo.abcs, 'arraybuffer'); + case SystemResourceId.PlayerglobalManifest: + return this._promiseFile(this.viewerPlayerglobalInfo.catalog, 'json'); + case SystemResourceId.ShellAbc: + return this._promiseFile(this.shellPath, 'arraybuffer'); + default: + return Promise.reject(new Error('Unsupported system resource id: ' + id)); + } + } + + private _promiseFile(path, responseType) { + return new Promise(function (resolve, reject) { + SWF.enterTimeline('Load file', path); + var xhr = new XMLHttpRequest(); + xhr.open('GET', path); + xhr.responseType = responseType; + xhr.onload = function () { + SWF.leaveTimeline(); + var response = xhr.response; + if (response) { + if (responseType === 'json' && xhr.responseType !== 'json') { + // some browsers (e.g. Safari) have no idea what json is + response = JSON.parse(response); + } + resolve(response); + } else { + reject('Unable to load ' + path + ': ' + xhr.statusText); + } + }; + xhr.onerror = function () { + SWF.leaveTimeline(); + reject('Unable to load: xhr error'); + }; + xhr.send(); + }); } } } \ No newline at end of file diff --git a/src/player/player.ts b/src/player/player.ts index 2f267a21a5..6359fdf894 100644 --- a/src/player/player.ts +++ b/src/player/player.ts @@ -49,6 +49,174 @@ module Shumway.Player { import DisplayParameters = Shumway.Remoting.DisplayParameters; + import FocusEventType = Remoting.FocusEventType; + import IGFXService = Shumway.Remoting.IGFXService; + import IGFXServiceObserver = Shumway.Remoting.IGFXServiceObserver; + + /** + * Base class implementation of the IGFXServer. The different transports shall + * inherit this class + */ + export class GFXServiceBase implements IGFXService { + _observers: IGFXServiceObserver[] = []; + + addObserver(observer: IGFXServiceObserver) { + this._observers.push(observer); + } + + removeObserver(observer: IGFXServiceObserver) { + var i = this._observers.indexOf(observer); + if (i >= 0) { + this._observers.splice(i, 1); + } + } + + update(updates: DataBuffer, assets: any[]): void { + throw new Error('This method is abstract'); + } + + updateAndGet(updates: DataBuffer, assets: any[]): any { + throw new Error('This method is abstract'); + } + + frame(): void { + throw new Error('This method is abstract'); + } + + videoControl(id: number, eventType: VideoControlEvent, data: any): any { + throw new Error('This method is abstract'); + } + + registerFont(syncId: number, data: any): Promise { + throw new Error('This method is abstract'); + } + + registerImage(syncId: number, symbolId: number, data: any): Promise { + throw new Error('This method is abstract'); + } + + fscommand(command: string, args: string): void { + throw new Error('This method is abstract'); + } + + public processUpdates(updates: DataBuffer, assets: any []) { + var deserializer = new Remoting.Player.PlayerChannelDeserializer(); + + deserializer.input = updates; + deserializer.inputAssets = assets; + + var message = deserializer.read(); + switch (message.tag) { + case MessageTag.KeyboardEvent: + this._observers.forEach(function (observer) { + observer.keyboardEvent(message); + }); + break; + case MessageTag.MouseEvent: + this._observers.forEach(function (observer) { + observer.mouseEvent(message); + }); + break; + case MessageTag.FocusEvent: + this._observers.forEach(function (observer) { + observer.focusEvent(message); + }); + break; + } + } + + public processDisplayParameters(displayParameters: DisplayParameters) { + this._observers.forEach(function (observer) { + observer.displayParameters(displayParameters); + }); + } + + public processVideoEvent(id: number, eventType: VideoPlaybackEvent, data: any) { + this._observers.forEach(function (observer) { + observer.videoEvent(id, eventType, data); + }); + } + } + + /** + * Helper class to handle GFXService notifications/events and forward them to + * the Player object. + */ + class GFXServiceObserver implements IGFXServiceObserver { + private _player: Player; + private _mouseEventDispatcher: MouseEventDispatcher; + private _keyboardEventDispatcher: KeyboardEventDispatcher; + private _videoEventListeners: {(eventType: VideoPlaybackEvent, data: any):void}[] = []; + private _writer: IndentingWriter; + + constructor (player: Player) { + this._player = player; + this._keyboardEventDispatcher = new KeyboardEventDispatcher(); + this._mouseEventDispatcher = new MouseEventDispatcher(); + this._writer = new IndentingWriter(); + } + + videoEvent(id: number, eventType: VideoPlaybackEvent, data: any) { + var listener = this._videoEventListeners[id]; + Debug.assert(listener, 'Video event listener is not found'); + listener(eventType, data); + } + + displayParameters(displayParameters: DisplayParameters) { + this._player._stage.setStageContainerSize(displayParameters.stageWidth, displayParameters.stageHeight, displayParameters.pixelRatio); + } + + focusEvent(data: any) { + var message: FocusEventData = data; + var focusType = message.type; + switch (focusType) { + case FocusEventType.DocumentHidden: + this._player._isPageVisible = false; + break; + case FocusEventType.DocumentVisible: + this._player._isPageVisible = true; + break; + case FocusEventType.WindowBlur: + // TODO: This is purposely disabled so that applications don't pause when they are out of + // focus while the debugging window is open. + // EventDispatcher.broadcastEventDispatchQueue.dispatchEvent(Event.getBroadcastInstance(Event.DEACTIVATE)); + this._player._hasFocus = false; + break; + case FocusEventType.WindowFocus: + EventDispatcher.broadcastEventDispatchQueue.dispatchEvent(Event.getBroadcastInstance(Event.ACTIVATE)); + this._player._hasFocus = true; + break; + } + + } + + keyboardEvent(data: any) { + var message: KeyboardEventData = data; + // If the stage doesn't have a focus then dispatch events on the stage + // directly. + var target = this._player._stage.focus ? this._player._stage.focus : this._player._stage; + this._keyboardEventDispatcher.target = target; + this._keyboardEventDispatcher.dispatchKeyboardEvent(message); + } + + mouseEvent(data: any) { + var message: MouseEventAndPointData = data; + this._mouseEventDispatcher.stage = this._player._stage; + var target = this._mouseEventDispatcher.handleMouseEvent(message); + if (traceMouseEventOption.value) { + this._writer.writeLn("Mouse Event: type: " + message.type + ", point: " + message.point + ", target: " + target + (target ? ", name: " + target._name : "")); + if (message.type === "click" && target) { + target.debugTrace(); + } + } + this._player._currentMouseTarget = this._mouseEventDispatcher.currentTarget; + } + + registerEventListener(id: number, listener: (eventType: VideoPlaybackEvent, data: any)=>void) { + this._videoEventListeners[id] = listener; + } + } + /** * Shumway Player * @@ -57,39 +225,17 @@ module Shumway.Player { */ export class Player implements IBitmapDataSerializer, IFSCommandListener, IVideoElementService, IAssetResolver, IRootElementService { - private _stage: flash.display.Stage; + _stage: flash.display.Stage; private _loader: flash.display.Loader; private _loaderInfo: flash.display.LoaderInfo; - private _syncTimeout: number; private _frameTimeout: number; private _eventLoopIsRunning: boolean; private _framesPlayed: number = 0; private _writer: IndentingWriter; - private _mouseEventDispatcher: MouseEventDispatcher; - private _keyboardEventDispatcher: KeyboardEventDispatcher; - private _videoEventListeners: {(eventType: VideoPlaybackEvent, data: any):void}[] = []; - /** - * Used to request things from the GFX remote. - */ - // TODO: rip this out - private _pendingPromises: PromiseWrapper [] = []; - - /** - * Returns an id that can be remoted to GFX. - */ - private _getNextAvailablePromiseId(): number { - var length = this._pendingPromises.length; - for (var i = 0; i < length; i++) { - if (!this._pendingPromises[i]) { - return i; - } - } - return length; - } - - public externalCallback: (functionName: string, args: any[]) => any = null; + private _gfxService: IGFXService; + private _gfxServiceObserver: GFXServiceObserver; /** * If set, overrides SWF file background color. @@ -124,12 +270,17 @@ module Shumway.Player { /** * Page Visibility API visible state. */ - private _isPageVisible = true; + _isPageVisible = true; /** * Page focus state. */ - private _hasFocus = true; + _hasFocus = true; + + /** + * Stage current mouse target. + */ + _currentMouseTarget: flash.display.InteractiveObject = null; /** * Page URL that hosts SWF. @@ -146,10 +297,13 @@ module Shumway.Player { */ private _loaderUrl: string = null; - constructor() { - this._keyboardEventDispatcher = new KeyboardEventDispatcher(); - this._mouseEventDispatcher = new MouseEventDispatcher(); + constructor(gfxService: IGFXService) { + release || Debug.assert(gfxService); this._writer = new IndentingWriter(); + this._gfxService = gfxService; + this._gfxServiceObserver = new GFXServiceObserver(this); + this._gfxService.addObserver(this._gfxServiceObserver); + AVM2.instance.globals['Shumway.Player.Utils'] = this; } @@ -160,16 +314,6 @@ module Shumway.Player { return this._stage; } - /** - * Abstract method to notify about updates. - * @param updates - * @param assets - */ - onSendUpdates(updates: DataBuffer, assets: Array, async: boolean = true): DataBuffer { - throw new Error('This method is abstract'); - return null; - } - /** * Whether we can get away with rendering at a lower rate. */ @@ -239,58 +383,8 @@ module Shumway.Player { return loaderContext; } - public processUpdates(updates: DataBuffer, assets: any []) { - var deserializer = new Remoting.Player.PlayerChannelDeserializer(); - var FocusEventType = Remoting.FocusEventType; - - deserializer.input = updates; - deserializer.inputAssets = assets; - - var message = deserializer.read(); - switch (message.tag) { - case MessageTag.KeyboardEvent: - // If the stage doesn't have a focus then dispatch events on the stage - // directly. - var target = this._stage.focus ? this._stage.focus : this._stage; - this._keyboardEventDispatcher.target = target; - this._keyboardEventDispatcher.dispatchKeyboardEvent(message); - break; - case MessageTag.MouseEvent: - this._mouseEventDispatcher.stage = this._stage; - var target = this._mouseEventDispatcher.handleMouseEvent(message); - if (traceMouseEventOption.value) { - this._writer.writeLn("Mouse Event: type: " + message.type + ", point: " + message.point + ", target: " + target + (target ? ", name: " + target._name : "")); - if (message.type === "click" && target) { - target.debugTrace(); - } - } - break; - case MessageTag.FocusEvent: - var focusType = (message).type; - switch (focusType) { - case FocusEventType.DocumentHidden: - this._isPageVisible = false; - break; - case FocusEventType.DocumentVisible: - this._isPageVisible = true; - break; - case FocusEventType.WindowBlur: - // TODO: This is purposely disabled so that applications don't pause when they are out of - // focus while the debugging window is open. - // EventDispatcher.broadcastEventDispatchQueue.dispatchEvent(Event.getBroadcastInstance(Event.DEACTIVATE)); - this._hasFocus = false; - break; - case FocusEventType.WindowFocus: - EventDispatcher.broadcastEventDispatchQueue.dispatchEvent(Event.getBroadcastInstance(Event.ACTIVATE)); - this._hasFocus = true; - break; - } - break; - } - } - private _pumpDisplayListUpdates(): void { - this.syncDisplayObject(this._stage); + this.syncDisplayObject(this._stage, true); } public syncDisplayObject(displayObject: flash.display.DisplayObject, async: boolean = true): DataBuffer { @@ -301,7 +395,7 @@ module Shumway.Player { serializer.outputAssets = assets; if (flash.display.Stage.isType(displayObject)) { - serializer.writeStage(displayObject, this._mouseEventDispatcher.currentTarget); + serializer.writeStage(displayObject, this._currentMouseTarget); } serializer.begin(displayObject); @@ -310,7 +404,12 @@ module Shumway.Player { updates.writeInt(Remoting.MessageTag.EOF); enterTimeline("remoting assets"); - var output = this.onSendUpdates(updates, assets, async); + var output; + if (async) { + this._gfxService.update(updates, assets); + } else { + output = this._gfxService.updateAndGet(updates, assets); + } leaveTimeline("remoting assets"); return output; @@ -324,7 +423,7 @@ module Shumway.Player { serializer.outputAssets = assets; serializer.writeRequestBitmapData(bitmapData); output.writeInt(Remoting.MessageTag.EOF); - return this.onSendUpdates(output, assets, false); + return this._gfxService.updateAndGet(output, assets); } public drawToBitmap(bitmapData: flash.display.BitmapData, source: Shumway.Remoting.IRemotable, matrix: flash.geom.Matrix = null, colorTransform: flash.geom.ColorTransform = null, blendMode: string = null, clipRect: flash.geom.Rectangle = null, smoothing: boolean = false) { @@ -351,16 +450,16 @@ module Shumway.Player { updates.writeInt(Shumway.Remoting.MessageTag.EOF); enterTimeline("sendUpdates"); - this.onSendUpdates(updates, assets, false); + this._gfxService.updateAndGet(updates, assets); // TODO replace to update() ? leaveTimeline("sendUpdates"); } public registerEventListener(id: number, listener: (eventType: VideoPlaybackEvent, data: any)=>void) { - this._videoEventListeners[id] = listener; + this._gfxServiceObserver.registerEventListener(id, listener); } public notifyVideoControl(id: number, eventType: VideoControlEvent, data: any): any { - return this.onVideoControl(id, eventType, data); + return this._gfxService.videoControl(id, eventType, data); } public executeFSCommand(command: string, args: string) { @@ -371,7 +470,7 @@ module Shumway.Player { default: somewhatImplemented('FSCommand ' + command); } - this.onFSCommand(command, args); + this._gfxService.fscommand(command, args); } public requestRendering(): void { @@ -449,7 +548,7 @@ module Shumway.Player { stage.setStageColor(ColorUtilities.RGBAToARGB(bgcolor)); if (self.displayParameters) { - self.processDisplayParameters(self.displayParameters); + self._gfxServiceObserver.displayParameters(self.displayParameters); } self._enterEventLoop(); @@ -494,7 +593,7 @@ module Shumway.Player { } this._stage.render(); this._pumpUpdates(); - this.onFrameProcessed(); + this._gfxService.frame(); } private _tracePlayer(): void { @@ -593,58 +692,27 @@ module Shumway.Player { }); } - public processExternalCallback(request) { - if (!this.externalCallback) { - return; - } - - try { - request.result = this.externalCallback(request.functionName, request.args); - } catch (e) { - request.error = e.message; - } - } - - public processVideoEvent(id: number, eventType: VideoPlaybackEvent, data: any) { - var listener = this._videoEventListeners[id]; - Debug.assert(listener, 'Video event listener is not found'); - listener(eventType, data); - } - - public processDisplayParameters(displayParameters: DisplayParameters) { - this._stage.setStageContainerSize(displayParameters.stageWidth, displayParameters.stageHeight, displayParameters.pixelRatio); - } - - onExternalCommand(command) { - throw new Error('This method is abstract'); - } - - onFSCommand(command: string, args: string) { - throw new Error('This method is abstract'); - } - - onVideoControl(id: number, eventType: VideoControlEvent, data: any): any { - throw new Error('This method is abstract'); - } - - onFrameProcessed() { - throw new Error('This method is abstract'); - } - - registerFontOrImage(symbol: Timeline.EagerlyResolvedSymbol, data: any): void { + registerFont(symbol: Timeline.EagerlyResolvedSymbol, data: any): void { release || assert(symbol.syncId); - symbol.resolveAssetPromise = new PromiseWrapper(); - this.registerFontOrImageImpl(symbol, data); + symbol.resolveAssetPromise = new PromiseWrapper(); // TODO no need for wrapper here, change to Promise + this._gfxService.registerFont(symbol.syncId, data).then(function (result) { + symbol.resolveAssetPromise.resolve(result); + }); // Fonts are immediately available in Firefox, so we can just mark the symbol as ready. - if (data.type === 'font' && inFirefox) { + if (inFirefox) { symbol.ready = true; } else { symbol.resolveAssetPromise.then(symbol.resolveAssetCallback, null); } } - protected registerFontOrImageImpl(symbol: Timeline.EagerlyResolvedSymbol, data: any) { - throw new Error('This method is abstract'); + registerImage(symbol: Timeline.EagerlyResolvedSymbol, data: any): void { + release || assert(symbol.syncId); + symbol.resolveAssetPromise = new PromiseWrapper(); // TODO no need for wrapper here, change to Promise + this._gfxService.registerImage(symbol.syncId, symbol.id, data).then(function (result) { + symbol.resolveAssetPromise.resolve(result); + }); + symbol.resolveAssetPromise.then(symbol.resolveAssetCallback, null); } } } diff --git a/src/player/test/testPlayer.ts b/src/player/test/testPlayer.ts index 02ea71bc1a..ec39cdb9cb 100644 --- a/src/player/test/testPlayer.ts +++ b/src/player/test/testPlayer.ts @@ -20,8 +20,9 @@ module Shumway.Player.Test { import VideoControlEvent = Shumway.Remoting.VideoControlEvent; - export class TestPlayer extends Player { + export class TestGFXService extends GFXServiceBase { private _worker; + private _fontOrImageRequests: PromiseWrapper[]; constructor() { super(); @@ -30,9 +31,10 @@ module Shumway.Player.Test { this._worker = Shumway.Player.Test.FakeSyncWorker.instance; this._worker.addEventListener('message', this._onWorkerMessage.bind(this)); this._worker.addEventListener('syncmessage', this._onSyncWorkerMessage.bind(this)); + this._fontOrImageRequests = []; } - public onSendUpdates(updates: DataBuffer, assets: Array, async: boolean = true): DataBuffer { + update(updates: DataBuffer, assets: any[]): void { var bytes = updates.getBytes(); var message = { type: 'player', @@ -40,30 +42,28 @@ module Shumway.Player.Test { assets: assets }; var transferList = [bytes.buffer]; - if (!async) { - var result = this._worker.postSyncMessage(message, transferList); - return DataBuffer.FromPlainObject(result); - } this._worker.postMessage(message, transferList); - return null; } - onExternalCommand(command) { - this._worker.postSyncMessage({ - type: 'external', - command: command - }); + updateAndGet(updates: DataBuffer, assets: any[]): any { + var bytes = updates.getBytes(); + var message = { + type: 'player', + updates: bytes, + assets: assets + }; + var transferList = [bytes.buffer]; + var result = this._worker.postSyncMessage(message, transferList); + return DataBuffer.FromPlainObject(result); } - onFSCommand(command: string, args: string) { + frame(): void { this._worker.postMessage({ - type: 'fscommand', - command: command, - args: args + type: 'frame' }); } - onVideoControl(id: number, eventType: VideoControlEvent, data: any): any { + videoControl(id: number, eventType: VideoControlEvent, data: any): any { return this._worker.postSyncMessage({ type: 'videoControl', id: id, @@ -72,22 +72,43 @@ module Shumway.Player.Test { }); } - onFrameProcessed() { - this._worker.postMessage({ - type: 'frame' - }); + registerFont(syncId: number, data: any): Promise { + var requestId = this._fontOrImageRequests.length; + var result = new PromiseWrapper(); + this._fontOrImageRequests[requestId] = result; + var message = { + type: 'registerFontOrImage', + syncId: syncId, + assetType: 'font', + data: data, + requestId: requestId + }; + this._worker.postMessage(message); + return result.promise; } - protected registerFontOrImageImpl(symbol: Timeline.EagerlyResolvedSymbol, data: any) { + registerImage(syncId: number, symbolId: number, data: any): Promise { + var requestId = this._fontOrImageRequests.length; + var result = new PromiseWrapper(); + this._fontOrImageRequests[requestId] = result; var message = { type: 'registerFontOrImage', - syncId: symbol.syncId, - symbolId: symbol.id, - assetType: data.type, + syncId: syncId, + symbolId: symbolId, + assetType: 'image', data: data, - resolve: symbol.resolveAssetPromise.resolve + requestId: requestId }; - return this._worker.postSyncMessage(message); + this._worker.postMessage(message); + return result.promise; + } + + fscommand(command: string, args: string): void { + this._worker.postMessage({ + type: 'fscommand', + command: command, + args: args + }); } private _onWorkerMessage(e) { @@ -100,16 +121,18 @@ module Shumway.Player.Test { var updates = DataBuffer.FromArrayBuffer(e.data.updates.buffer); this.processUpdates(updates, e.data.assets); break; - case 'externalCallback': - this.processExternalCallback(data.request); - e.handled = true; - return; case 'videoPlayback': this.processVideoEvent(data.id, data.eventType, data.data); return; case 'displayParameters': this.processDisplayParameters(data.params); break; + case 'registerFontOrImageResponse': + var request = this._fontOrImageRequests[data.requestId]; + release || Debug.assert(request); + delete this._fontOrImageRequests[data.requestId]; + request.resolve(data.result); + break; } } diff --git a/src/player/window/windowPlayer.ts b/src/player/window/windowPlayer.ts index 66aaef13dd..0523169aa5 100644 --- a/src/player/window/windowPlayer.ts +++ b/src/player/window/windowPlayer.ts @@ -20,12 +20,14 @@ module Shumway.Player.Window { import VideoControlEvent = Shumway.Remoting.VideoControlEvent; - export class WindowPlayer extends Player { + export class WindowGFXService extends GFXServiceBase { private _window; private _parent; + private _fontOrImageRequests: PromiseWrapper[]; constructor(window, parent?) { super(); + this._window = window; this._parent = parent || window.parent; this._window.addEventListener('message', function (e) { @@ -34,9 +36,10 @@ module Shumway.Player.Window { this._window.addEventListener('syncmessage', function (e) { this.onWindowMessage(e.detail, false); }.bind(this)); + this._fontOrImageRequests = []; } - onSendUpdates(updates: DataBuffer, assets: Array, async: boolean = true): DataBuffer { + update(updates: DataBuffer, assets: any[]): void { var bytes = updates.getBytes(); var message = { type: 'player', @@ -45,36 +48,33 @@ module Shumway.Player.Window { result: undefined }; var transferList = [bytes.buffer]; - if (!async) { - // TODO var result = this._parent.postSyncMessage(message, '*', transferList); - var event = this._parent.document.createEvent('CustomEvent'); - event.initCustomEvent('syncmessage', false, false, message); - this._parent.dispatchEvent(event); - var result = message.result; - return DataBuffer.FromPlainObject(result); - } this._parent.postMessage(message, '*', transferList); - return null; } - onExternalCommand(command) { + updateAndGet(updates: DataBuffer, assets: any[]): any { + var bytes = updates.getBytes(); + var message = { + type: 'player', + updates: bytes, + assets: assets, + result: undefined + }; + var transferList = [bytes.buffer]; + // TODO var result = this._parent.postSyncMessage(message, '*', transferList); var event = this._parent.document.createEvent('CustomEvent'); - event.initCustomEvent('syncmessage', false, false, { - type: 'external', - request: command - }); + event.initCustomEvent('syncmessage', false, false, message); this._parent.dispatchEvent(event); + var result = message.result; + return DataBuffer.FromPlainObject(result); } - onFSCommand(command: string, args: string) { + frame(): void { this._parent.postMessage({ - type: 'fscommand', - command: command, - args: args + type: 'frame' }, '*'); } - onVideoControl(id: number, eventType: VideoControlEvent, data: any): any { + videoControl(id: number, eventType: VideoControlEvent, data: any): any { var event = this._parent.document.createEvent('CustomEvent'); event.initCustomEvent('syncmessage', false, false, { type: 'videoControl', @@ -87,23 +87,43 @@ module Shumway.Player.Window { return event.detail.result; } - onFrameProcessed() { - this._parent.postMessage({ - type: 'frame' - }, '*'); + registerFont(syncId: number, data: any): Promise { + var requestId = this._fontOrImageRequests.length; + var result = new PromiseWrapper(); + this._fontOrImageRequests[requestId] = result; + var message = { + type: 'registerFontOrImage', + syncId: syncId, + assetType: 'font', + data: data, + requestId: requestId + }; + this._parent.postMessage(message, '*'); + return result.promise; } - protected registerFontOrImageImpl(symbol: Timeline.EagerlyResolvedSymbol, data: any) { - var event = this._parent.document.createEvent('CustomEvent'); - event.initCustomEvent('syncmessage', false, false, { + registerImage(syncId: number, symbolId: number, data: any): Promise { + var requestId = this._fontOrImageRequests.length; + var result = new PromiseWrapper(); + this._fontOrImageRequests[requestId] = result; + var message = { type: 'registerFontOrImage', - syncId: symbol.syncId, - symbolId: symbol.id, - assetType: data.type, + syncId: syncId, + symbolId: symbolId, + assetType: 'image', data: data, - resolve: symbol.resolveAssetPromise.resolve - }); - this._parent.dispatchEvent(event); + requestId: requestId + }; + this._parent.postMessage(message, '*'); + return result.promise; + } + + fscommand(command: string, args: string): void { + this._parent.postMessage({ + type: 'fscommand', + command: command, + args: args + }, '*'); } private onWindowMessage(data, async) { @@ -114,9 +134,6 @@ module Shumway.Player.Window { var updates = DataBuffer.FromArrayBuffer(data.updates.buffer); this.processUpdates(updates, data.assets); break; - case 'externalCallback': - this.processExternalCallback(data.request); - break; case 'videoPlayback': this.processVideoEvent(data.id, data.eventType, data.data); break; @@ -145,7 +162,7 @@ module Shumway.Player.Window { break; } this._parent.postMessage({ - type:'timelineResponse', + type: 'timelineResponse', request: data.request, timeline: Shumway.Player.timelineBuffer }, '*'); @@ -156,7 +173,7 @@ module Shumway.Player.Window { break; } this._parent.postMessage({ - type:'timelineResponse', + type: 'timelineResponse', request: data.request, timeline: Shumway.SWF.timelineBuffer }, '*'); diff --git a/src/shell/shell.ts b/src/shell/shell.ts index 2eacaa68a1..279931b6db 100644 --- a/src/shell/shell.ts +++ b/src/shell/shell.ts @@ -116,30 +116,49 @@ module Shumway.Shell { import Compiler = Shumway.AVM2.Compiler; - class ShellPlayer extends Shumway.Player.Player { - onSendUpdates(updates:DataBuffer, assets:Array, async:boolean = true):DataBuffer { + class ShellGFXServer extends Shumway.Player.GFXServiceBase { + update(updates: DataBuffer, assets: any[]): void { var bytes = updates.getBytes(); - if (!async) { - // Simulating text field metrics - var buffer = new DataBuffer() - buffer.write2Ints(1, 1); // textWidth, textHeight - buffer.writeInt(0); // offsetX - buffer.writeInt(0); // numLines - buffer.position = 0; - return buffer; - } // console.log('Updates sent'); return null; } - onFSCommand(command: string, args: string) { + + updateAndGet(updates: DataBuffer, assets: any[]): any { + var bytes = updates.getBytes(); + + // Simulating text field metrics + var buffer = new DataBuffer(); + buffer.write2Ints(1, 1); // textWidth, textHeight + buffer.writeInt(0); // offsetX + buffer.writeInt(0); // numLines + buffer.position = 0; + return buffer; + } + + frame(): void { + // console.log('Frame'); + } + + videoControl(id: number, eventType: Shumway.Remoting.VideoControlEvent, data: any): any { + // console.log('videoControl'); + } + + registerFont(syncId: number, data: any): Promise { + // console.log('registerFont'); + return Promise.resolve(undefined); + } + + registerImage(syncId: number, symbolId: number, data: any): Promise { + // console.log('registerImage'); + return Promise.resolve({width: 100, height: 50}); + } + + fscommand(command: string, args: string): void { if (command === 'quit') { // console.log('Player quit'); microTaskQueue.stop(); } } - onFrameProcessed() { - // console.log('Frame'); - } } var verbose = false; @@ -320,7 +339,7 @@ module Shumway.Shell { flash.display.Loader.reset(); flash.display.DisplayObject.reset(); flash.display.MovieClip.reset(); - var player = new ShellPlayer(); + var player = new Shumway.Player.Player(new ShellGFXServer()); player.load(file); } var asyncLoading = true; diff --git a/test/harness/slave.js b/test/harness/slave.js index 91892b530b..4cc2d65f27 100644 --- a/test/harness/slave.js +++ b/test/harness/slave.js @@ -110,11 +110,15 @@ function loadMovie(path, reportFrames) { Shumway.FileLoadingService.instance = new Shumway.Player.BrowserFileLoadingService(); Shumway.FileLoadingService.instance.init(path); - Shumway.createAVM2(builtinPath, playerglobalInfo, sysMode, appMode, function (avm2) { + Shumway.SystemResourcesLoadingService.instance = + new Shumway.Player.BrowserSystemResourcesLoadingService(builtinPath, playerglobalInfo); + + Shumway.createAVM2(Shumway.AVM2LoadLibrariesFlags.Builtin | Shumway.AVM2LoadLibrariesFlags.Playerglobal, sysMode, appMode).then(function (avm2) { easelHost = new Shumway.GFX.Test.TestEaselHost(easel); initEaselHostCallbacks(); - player = new Shumway.Player.Test.TestPlayer(); + var gfxService = new Shumway.Player.Test.TestGFXService(); + player = new Shumway.Player.Player(gfxService); player.stageAlign = 'tl'; player.stageScale = 'noscale'; player.displayParameters = easel.getDisplayParameters(); diff --git a/test/harness/slavePlayer.js b/test/harness/slavePlayer.js index f0d5d1a878..7324e97430 100644 --- a/test/harness/slavePlayer.js +++ b/test/harness/slavePlayer.js @@ -79,9 +79,12 @@ function runSwfPlayer(flashParams) { var movieParams = flashParams.movieParams; var objectParams = flashParams.objectParams; var movieUrl = flashParams.url; - Shumway.createAVM2(builtinPath, viewerPlayerglobalInfo, sysMode, appMode, function (avm2) { + Shumway.SystemResourcesLoadingService.instance = + new Shumway.Player.BrowserSystemResourcesLoadingService(builtinPath, viewerPlayerglobalInfo); + Shumway.createAVM2(Shumway.AVM2LoadLibrariesFlags.Builtin | Shumway.AVM2LoadLibrariesFlags.Playerglobal, sysMode, appMode).then(function (avm2) { function runSWF(file) { - var player = new Shumway.Player.Window.WindowPlayer(window, window.parent); + var gfxService = new Shumway.Player.Window.WindowGFXService(window, window.parent); + var player = new Shumway.Player.Player(gfxService); player.stageAlign = 'tl'; player.stageScale = 'noscale'; player.load(file); diff --git a/web/iframe/viewerPlayer.js b/web/iframe/viewerPlayer.js index d0311b2960..2e3f1e01fd 100644 --- a/web/iframe/viewerPlayer.js +++ b/web/iframe/viewerPlayer.js @@ -86,12 +86,15 @@ function runSwfPlayer(flashParams) { var asyncLoading = true; var baseUrl = flashParams.baseUrl; var movieUrl = flashParams.url; - Shumway.createAVM2(builtinPath, viewerPlayerglobalInfo, sysMode, appMode, function (avm2) { + Shumway.SystemResourcesLoadingService.instance = + new Shumway.Player.BrowserSystemResourcesLoadingService(builtinPath, viewerPlayerglobalInfo); + Shumway.createAVM2(Shumway.AVM2LoadLibrariesFlags.Builtin | Shumway.AVM2LoadLibrariesFlags.Playerglobal, sysMode, appMode).then(function (avm2) { function runSWF(file, buffer, baseUrl) { var movieParams = flashParams.movieParams; var objectParams = flashParams.objectParams; - player = new Shumway.Player.Window.WindowPlayer(window, window.parent); + var gfxService = new Shumway.Player.Window.WindowGFXService(window, window.parent); + player = new Shumway.Player.Player(gfxService); player.defaultStageColor = flashParams.bgcolor; player.movieParams = movieParams; player.stageAlign = (objectParams && (objectParams.salign || objectParams.align)) || '';