radio/libjs/wController/player.js

306 lines
9.8 KiB
JavaScript

"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;