"use strict"; var Uint64 = require("../wType/uint64"); var Address = require("../wType/address"); var Controller = require("./controller"); var Button = require("./button"); var ImageById = require("./imageById"); var Vocabulary = require("./vocabulary"); var Audio = require("./file/audio"); var Enum = require("../utils/enum"); var StateMachine = require("../utils/stateMachine"); var Player = Controller.inherit({ className: "Player", constructor: function(addr) { Controller.fn.constructor.call(this, addr); this.controls = Object.create(null); this.views = Object.create(null); this.mode = PlayerMode.straight.playback; this._audio = null; this._sound = new window.Audio(); this._ctx = new AudioContext(); this._currentTime = 0; this._createStateMachine(); this._proxySchedule = this._schedule.bind(this); this.addHandler("get"); this.addHandler("viewsChange"); this.addHandler("play"); this.addHandler("pause"); }, destructor: function() { this._fsm.destructor(); this._sound.pause(); this._ctx.close(); Controller.fn.destructor.call(this); }, _addControl: function(type, address) { var t = type.valueOf(); if (this.controls[t] !== undefined) { throw new Error("An attempt to add multiple instances of " + ItemType.reversed[t] + " into Player"); } if (ItemType.reversed[t] !== undefined) { switch (t) { case ItemType.straight.playPause: var btn = new Button(address.clone()); btn.itemType = t; this.controls[t] = btn; this.addController(btn); this.trigger("newElement", btn, t); break; default: this.trigger("serviceMessage", "An attempt to add ItemType " + ItemType.reversed[t] + " to controls of the Player, but it's not qualified to be a control"); } } else { this.trigger("serviceMessage", "An unrecgnized item ItemType in Player: " + t); } }, _addView: function(type, address) { var t = type.valueOf(); var ctrl; var supported = false; if (this.views[t] !== undefined) { throw new Error("An attempt to add multiple instances of " + ItemType.reversed[t] + " into Player"); } if (ItemType.reversed[t] !== undefined) { switch (t) { case ItemType.straight.queue: this.trigger("serviceMessage", "Queue is not supported yet in Player"); break; case ItemType.straight.currentPlayback: ctrl = new Vocabulary(address.clone()); ctrl.on("newElement", this._onNewPlayBackElement, this); ctrl.on("removeElement", this._onNewRemoveBackElement, this); supported = true; break; case ItemType.straight.picture: ctrl = new ImageById(null, address.back()); ctrl.ItemType = t; this.views[t] = ctrl; this.trigger("newElement", ctrl, t); supported = false; //just to avoid adding with addController, since ImageById is not a controller break; default: this.trigger("serviceMessage", "An attempt to add ItemType " + ItemType.reversed[t] + " to views of the Player, but it's not qualified to be a view"); } } else { this.trigger("serviceMessage", "An unrecgnized item ItemType in Player: " + t); } if (supported) { ctrl.ItemType = t; this.views[t] = ctrl; this.addController(ctrl); this.trigger("newElement", ctrl, t); } }, _createStateMachine: function() { this._fsm = new StateMachine("initial", graphs[this.mode]); this._fsm.on("stateChanged", this._onStateChanged, this); }, _h_get: function(ev) { var data = ev.getData(); var controls = data.at("controls"); var views = data.at("views"); var mode = data.at("mode").valueOf(); var size, i, vc; size = controls.length(); for (i = 0; i < size; ++i) { vc = controls.at(i); this._addControl(vc.at("type"), vc.at("address")); } size = views.length(); for (i = 0; i < size; ++i) { vc = views.at(i); this._addView(vc.at("type"), vc.at("address")); } if (this.mode !== mode) { if (PlayerMode.reversed[mode] === undefined) { throw new Error("Unsupported mode of player: " + mode); } this.mode = mode; } this.initialized = true; this.trigger("data"); }, _h_pause: function(ev) { this._fsm.manipulation("plause"); }, _h_play: function(ev) { this._fsm.manipulation("play"); }, _h_viewsChange: function(ev) { var data = ev.getData(); var add = data.at("add"); var remove = data.at("remove"); var size, i, vc; size = remove.length(); for (i = 0; i < size; ++i) { this._removeView(remove.at(i)); } size = add.length(); for (i = 0; i < size; ++i) { vc = add.at(i); this._addView(vc.at("type"), vc.at("address")); } }, _onAudioNewFrames: function(frames) { this._ctx.decodeAudioData(frames.valueOf(), this._proxySchedule); this._fsm.manipulation("newFrames"); if (this._audio.hasMore()) { this._audio.requestMore(); } else { this._fsm.manipulation("noMoreFrames"); } }, _onNewPlayBackElement: function(key, element) { switch (key) { case "image": var address = new Address(["images", element.toString()]); this._addView(new Uint64(ItemType.straight.picture), address); address.destructor(); break; case "audio": if (this.mode === PlayerMode.straight.playback) { this._audio = new Audio(new Address(["music", element.toString()])); this.addForeignController("Corax", this._audio); this._audio.on("newFrames", this._onAudioNewFrames, this); this._fsm.manipulation("controller"); } break; } }, _onNewRemoveBackElement: function(key) { switch (key) { case "image": this._removeView(new Uint64(ItemType.straight.picture)); break; case "audio": this.removeForeignController(this._audio); this._audio.destructor(); this._audio = null; } }, _onStateChanged: function(e) { switch (e.newState) { case "initial": break; case "initialPlaying": break; case "hasController": break; case "hasControllerPlaying": if (this._audio.hasMore()) { this._audio.requestMore(); } else { this._fsm.manipulation("noMoreFrames"); } break; case "paused": switch (e.oldState) { case "playing": this._sound.pause(); break; } break; case "pausedAllLoaded": switch (e.oldState) { case "playingAllLoaded": this._sound.pause(); break; } break; case "playing": this._sound.play(); break; case "playingAllLoaded": switch (e.oldState) { case "pausedAllLoaded": this._sound.play(); break; } break; } }, _removeControl: function(type) { //TODO }, _removeView: function(type) { //TODO }, _schedule: function(buffer) { var source = this._ctx.createBufferSource(); source.buffer = buffer; source.connect(this._ctx.destination); source.start(this._currentTime); this._currentTime += buffer.duration; } }); var ItemType = new Enum("ItemType"); ItemType.add("playPause"); ItemType.add("currentPlayback"); ItemType.add("queue"); ItemType.add("picture"); var PlayerMode = new Enum("PlayerMode"); PlayerMode.add("playback"); Player.ItemType = ItemType; var graphs = Object.create(null); graphs[PlayerMode.straight.playback] = { "initial": { controller: "hasController", play: "initialPlaying" }, "initialPlaying": { pause: "initial", controller: "hasControllerPlaying" }, "hasController": { newFrames: "paused", play: "hasControllerPlaying" }, "hasControllerPlaying": { newFrames: "playing", pause: "hasController" }, "paused": { play: "playing", noMoreFrames: "pausedAllLoaded" }, "pausedAllLoaded": { play: "playingAllLoaded" }, "playing": { pause: "pause", noMoreFrames: "playingAllLoaded" }, "playingAllLoaded": { pause: "pausedAllLoaded" } } module.exports = Player;