Decoding with my own translation of libmad, loading and playback progress, next and prev songs
This commit is contained in:
parent
0e31c5fdf2
commit
e690d67b80
21 changed files with 290 additions and 11796 deletions
|
@ -9,79 +9,16 @@ var Audio = File.inherit({
|
|||
className: "Audio",
|
||||
constructor: function Audio(addr) {
|
||||
File.fn.constructor.call(this, addr);
|
||||
|
||||
this._loadedFrames = 0;
|
||||
this._totalFrames = 0;
|
||||
this._waitingForFrames = false;
|
||||
this._frames = new Vector();
|
||||
|
||||
this.addHandler("responseFrames");
|
||||
},
|
||||
destructor: function() {
|
||||
this._frames.destructor();
|
||||
|
||||
File.fn.destructor.call(this);
|
||||
},
|
||||
hasMore: function() {
|
||||
return this._totalFrames > this._loadedFrames;
|
||||
return this.getSize() > this.data.length();
|
||||
},
|
||||
_getAdditional: function(add) {
|
||||
var ac = File.fn._getAdditional.call(this, add);
|
||||
|
||||
if (ac) {
|
||||
this._loadedFrames = 0;
|
||||
this._totalFrames = this._additional.at("framesAmount").valueOf();
|
||||
this._waitingForFrames = false;
|
||||
}
|
||||
|
||||
return ac;
|
||||
getBitrate: function() {
|
||||
return this._additional.at("bitrate").valueOf();
|
||||
},
|
||||
_h_responseFrames: function(ev) {
|
||||
if (this._waitingForFrames === true) {
|
||||
var data = ev.getData();
|
||||
|
||||
var success = data.at("result").valueOf();
|
||||
if (success === 0) {
|
||||
var frames = data.at("frames");
|
||||
var amount = frames.length();
|
||||
var buffer = new ArrayBuffer(0);
|
||||
|
||||
for (var i = 0; i < amount; ++i) {
|
||||
var blob = frames.at(i).clone();
|
||||
this._frames.push(blob);
|
||||
var frame = blob.valueOf();
|
||||
|
||||
var newArr = new Uint8Array(buffer.byteLength + frame.byteLength);
|
||||
newArr.set(new Uint8Array(buffer), 0);
|
||||
newArr.set(new Uint8Array(frame), buffer.byteLength);
|
||||
buffer = newArr.buffer;
|
||||
}
|
||||
|
||||
|
||||
this._loadedFrames += amount;
|
||||
this._waitingForFrames = false;
|
||||
this.trigger("newFrames", buffer);
|
||||
}
|
||||
}
|
||||
},
|
||||
requestMore: function() {
|
||||
if (!this._waitingForFrames) {
|
||||
if (this._registered && this._subscribed) {
|
||||
var allowed = this._totalFrames - this._loadedFrames;
|
||||
|
||||
if (allowed > 0) {
|
||||
var vc = new Vocabulary();
|
||||
vc.insert("index", new Uint64(this._loadedFrames));
|
||||
vc.insert("amount", new Uint64(Math.min(framePortion, allowed)));
|
||||
|
||||
this.send(vc, "requestFrames");
|
||||
this._waitingForFrames = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
getDuration: function() {
|
||||
return this._additional.at("duration").valueOf();
|
||||
}
|
||||
});
|
||||
|
||||
var framePortion = 10;
|
||||
|
||||
module.exports = Audio;
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
var Controller = require("../controller");
|
||||
var WVocabulary = require("../../wType/vocabulary");
|
||||
var Uint64 = require("../../wType/uint64");
|
||||
var Blob = require("../../wType/blob");
|
||||
|
||||
var File = Controller.inherit({
|
||||
"className": "File",
|
||||
|
@ -10,17 +12,17 @@ var File = Controller.inherit({
|
|||
|
||||
this._hasData = false;
|
||||
this._hasAdditional = false;
|
||||
this.data = null;
|
||||
this.data = new Blob();
|
||||
this._additional = null;
|
||||
this._waitingForSlice = false;
|
||||
this._need = 0;
|
||||
|
||||
this.addHandler("get");
|
||||
this.addHandler("getAdditional");
|
||||
this.addHandler("getSlice");
|
||||
},
|
||||
"destructor": function() {
|
||||
if (this._hasData) {
|
||||
this.data.destructor();
|
||||
}
|
||||
this.data.destructor();
|
||||
if (this._hasAdditional) {
|
||||
this._additional.destructor();
|
||||
}
|
||||
|
@ -46,6 +48,9 @@ var File = Controller.inherit({
|
|||
"getMimeType": function() {
|
||||
return this._additional.at("mimeType").toString();
|
||||
},
|
||||
"getSize": function() {
|
||||
return this._additional.at("size").valueOf();
|
||||
},
|
||||
"_h_get": function(ev) {
|
||||
var dt = ev.getData();
|
||||
|
||||
|
@ -55,7 +60,9 @@ var File = Controller.inherit({
|
|||
}
|
||||
|
||||
this._hasData = true;
|
||||
var oldData = this.data;
|
||||
this.data = dt.at("data").clone();
|
||||
oldData.destructor();
|
||||
this.trigger("data");
|
||||
},
|
||||
"_h_getAdditional": function(ev) {
|
||||
|
@ -69,6 +76,24 @@ var File = Controller.inherit({
|
|||
this.trigger("ready");
|
||||
}
|
||||
},
|
||||
"_h_getSlice": function(ev) {
|
||||
if (this._waitingForSlice) {
|
||||
this._waitingForSlice = false;
|
||||
var vc = ev.getData();
|
||||
if (vc.at("result").valueOf() == 0) {
|
||||
var slice = vc.at("slice");
|
||||
this.data["+="](slice);
|
||||
this.trigger("slice", slice);
|
||||
|
||||
if (this.getSize() === this.data.length()) {
|
||||
this._hasData = true;
|
||||
this.trigger("data");
|
||||
}
|
||||
} else {
|
||||
this.trigger("serviceMessage", "Error receiving slice from " + this._pairAddress.toString(), 1);
|
||||
}
|
||||
}
|
||||
},
|
||||
"needData": function() {
|
||||
if (this._need === 0) {
|
||||
var vc = new WVocabulary();
|
||||
|
@ -77,6 +102,30 @@ var File = Controller.inherit({
|
|||
}
|
||||
++this._need;
|
||||
},
|
||||
"requestSlice": function(size) {
|
||||
if (this._hasAdditional) {
|
||||
if (this._waitingForSlice) {
|
||||
throw new Error("An attempt to request a slice of data from " + this._pairAddress.toString() + " while another request is in progress");
|
||||
}
|
||||
var begin = this.data.length();
|
||||
var newSize = Math.min(size, this.getSize() - begin);
|
||||
if (newSize !== size) {
|
||||
//TODO may be inform the developer about that?
|
||||
}
|
||||
if (newSize === 0) {
|
||||
return; //TODO may be inform the developer about that?
|
||||
}
|
||||
var vc = new WVocabulary();
|
||||
vc.insert("begin", new Uint64(begin));
|
||||
vc.insert("size", new Uint64(newSize));
|
||||
|
||||
this.send(vc, "getSlice");
|
||||
this._waitingForSlice = true;
|
||||
|
||||
} else {
|
||||
throw new Error("An attempt to request a slice of data from " + this._pairAddress.toString() + " before controller got initialized");
|
||||
}
|
||||
},
|
||||
"subscribe": function() {
|
||||
Controller.fn.subscribe.call(this);
|
||||
|
||||
|
|
|
@ -8,16 +8,11 @@ var Button = require("./button");
|
|||
var ImageById = require("./imageById");
|
||||
var Vocabulary = require("./vocabulary");
|
||||
var Audio = require("./file/audio");
|
||||
var Model = require("./localModel");
|
||||
|
||||
var Enum = require("../utils/enum");
|
||||
var StateMachine = require("../utils/stateMachine");
|
||||
|
||||
var Source = AV.EventEmitter.extend(function() {
|
||||
this.prototype.start = function(){}
|
||||
this.prototype.pause = function(){}
|
||||
this.prototype.reset = function(){}
|
||||
});
|
||||
|
||||
var Player = Controller.inherit({
|
||||
className: "Player",
|
||||
constructor: function(addr) {
|
||||
|
@ -26,6 +21,7 @@ var Player = Controller.inherit({
|
|||
this.controls = Object.create(null);
|
||||
this.views = Object.create(null);
|
||||
this.mode = PlayerMode.straight.playback;
|
||||
this.progress = new ProgressModel();
|
||||
this._audio = null;
|
||||
this._createStateMachine();
|
||||
this._createPlayingInfrastructure();
|
||||
|
@ -34,10 +30,14 @@ var Player = Controller.inherit({
|
|||
this.addHandler("viewsChange");
|
||||
this.addHandler("play");
|
||||
this.addHandler("pause");
|
||||
|
||||
this._playbackInterval = setInterval(this._onInterval.bind(this), 250);
|
||||
},
|
||||
destructor: function() {
|
||||
this._clearInterval(this._playbackInterval);
|
||||
this._destroyPlayingInfrastructure();
|
||||
this._fsm.destructor();
|
||||
this._player.stop();
|
||||
this.progress.destructor();
|
||||
|
||||
Controller.fn.destructor.call(this);
|
||||
},
|
||||
|
@ -109,15 +109,16 @@ var Player = Controller.inherit({
|
|||
this.trigger("newElement", ctrl, t);
|
||||
}
|
||||
},
|
||||
_createPlayingInfrastructure() {
|
||||
if (this._source) {
|
||||
this._source.reset();
|
||||
this._asset.stop();
|
||||
this._player.stop();
|
||||
}
|
||||
this._source = new Source();
|
||||
this._asset = new AV.Asset(this._source);
|
||||
this._player = new AV.Player(this._asset);
|
||||
_createPlayingInfrastructure: function() {
|
||||
this._ctx = new AudioContext();
|
||||
this._decoder = new Mp3Decoder();
|
||||
this._currentTime = 0;
|
||||
|
||||
this._ctx.suspend();
|
||||
},
|
||||
_destroyPlayingInfrastructure: function() {
|
||||
this._ctx.close();
|
||||
this._decoder.delete();
|
||||
},
|
||||
_createStateMachine: function() {
|
||||
this._fsm = new StateMachine("initial", graphs[this.mode]);
|
||||
|
@ -177,13 +178,28 @@ var Player = Controller.inherit({
|
|||
this._addView(vc.at("type"), vc.at("address"));
|
||||
}
|
||||
},
|
||||
_onAudioNewFrames: function(frames) {
|
||||
var data = new Uint8Array(frames);
|
||||
this._source.emit("data", new AV.Buffer(data));
|
||||
_onAudioNewSlice: function(frames) {
|
||||
var arr = new Uint8Array(frames.valueOf());
|
||||
this._decoder.addFragment(arr);
|
||||
|
||||
while (this._decoder.hasMore()) {
|
||||
var sb = this._decoder.decode(9999);
|
||||
if (sb === undefined) {
|
||||
break;
|
||||
} else {
|
||||
var src = this._ctx.createBufferSource();
|
||||
src.buffer = sb;
|
||||
src.connect(this._ctx.destination);
|
||||
src.start(this._currentTime);
|
||||
this._currentTime += sb.duration;
|
||||
}
|
||||
}
|
||||
|
||||
this.progress.setLoad(this._currentTime / this._audio.getDuration());
|
||||
|
||||
this._fsm.manipulation("newFrames");
|
||||
if (this._audio.hasMore()) {
|
||||
this._audio.requestMore();
|
||||
this._audio.requestSlice(audioPortion);
|
||||
} else {
|
||||
this._fsm.manipulation("noMoreFrames");
|
||||
}
|
||||
|
@ -191,6 +207,11 @@ var Player = Controller.inherit({
|
|||
_onControllerReady: function() {
|
||||
this._fsm.manipulation("controllerReady");
|
||||
},
|
||||
_onInterval: function() {
|
||||
if (this._audio && this._audio.initialized) {
|
||||
this.progress.setPlayback(this._ctx.currentTime / this._audio.getDuration());
|
||||
}
|
||||
},
|
||||
_onNewPlayBackElement: function(key, element) {
|
||||
switch (key) {
|
||||
case "image":
|
||||
|
@ -202,7 +223,7 @@ var Player = Controller.inherit({
|
|||
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._audio.on("slice", this._onAudioNewSlice, this);
|
||||
this._audio.on("ready", this._onControllerReady, this);
|
||||
this._fsm.manipulation("controller");
|
||||
}
|
||||
|
@ -228,16 +249,18 @@ var Player = Controller.inherit({
|
|||
this.removeForeignController(this._audio);
|
||||
this._audio.destructor();
|
||||
this._audio = null;
|
||||
this._destroyPlayingInfrastructure();
|
||||
this._createPlayingInfrastructure();
|
||||
}
|
||||
break;
|
||||
case "initialPlaying":
|
||||
if (e.manipulation === "noController") {
|
||||
this._player.pause();
|
||||
this._ctx.suspend();
|
||||
|
||||
this.removeForeignController(this._audio);
|
||||
this.audio.destructor();
|
||||
this._audio = null;
|
||||
this._destroyPlayingInfrastructure();
|
||||
this._createPlayingInfrastructure();
|
||||
}
|
||||
break;
|
||||
|
@ -249,9 +272,9 @@ var Player = Controller.inherit({
|
|||
break;
|
||||
case "hasControllerPlaying":
|
||||
if (this._audio.hasMore()) {
|
||||
this._audio.requestMore();
|
||||
this._audio.requestSlice(audioPortion);
|
||||
|
||||
this._player.play(); //todo temporal
|
||||
this._ctx.resume(); //todo temporal
|
||||
} else {
|
||||
this._fsm.manipulation("noMoreFrames");
|
||||
}
|
||||
|
@ -259,24 +282,24 @@ var Player = Controller.inherit({
|
|||
case "paused":
|
||||
switch (e.oldState) {
|
||||
case "playing":
|
||||
this._player.pause();
|
||||
this._ctx.suspend();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "pausedAllLoaded":
|
||||
switch (e.oldState) {
|
||||
case "playingAllLoaded":
|
||||
this._player.pause();
|
||||
this._ctx.suspend();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "playing":
|
||||
this._player.play();
|
||||
this._ctx.resume();
|
||||
break;
|
||||
case "playingAllLoaded":
|
||||
switch (e.oldState) {
|
||||
case "pausedAllLoaded":
|
||||
this._player.play();
|
||||
this._ctx.resume();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
@ -371,4 +394,29 @@ graphs[PlayerMode.straight.playback] = {
|
|||
}
|
||||
}
|
||||
|
||||
var audioPortion = 1024 * 50;
|
||||
|
||||
var ProgressModel = Model.inherit({
|
||||
className: "ProgressModel",
|
||||
constructor: function(properties) {
|
||||
Model.fn.constructor.call(this, properties);
|
||||
|
||||
this.load = 0;
|
||||
this.playback = 0;
|
||||
this.initialized = true;
|
||||
},
|
||||
setLoad: function(l) {
|
||||
if (l !== this.load) {
|
||||
this.load = l;
|
||||
this.trigger("load", l);
|
||||
}
|
||||
},
|
||||
setPlayback: function(p) {
|
||||
if (p !== this.playback) {
|
||||
this.playback = p;
|
||||
this.trigger("playback", p);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Player;
|
||||
|
|
|
@ -17,6 +17,36 @@ var Blob = Object.inherit({
|
|||
|
||||
return this.size() == other.size(); //TODO let's pretend one shall never wish to compare blobs)
|
||||
},
|
||||
"+=": function(other) {
|
||||
if (this.getType() !== other.getType()) {
|
||||
throw new Error("An attempt to add and assign an " + other.className + " to " + this.className);
|
||||
}
|
||||
|
||||
var newData = new ArrayBuffer(this._data.byteLength + other._data.byteLength);
|
||||
var newView = new Uint8Array(newData);
|
||||
var thisView = new Uint8Array(this._data);
|
||||
var otherView = new Uint8Array(other._data);
|
||||
|
||||
newView.set(thisView, 0);
|
||||
newView.set(otherView, this._data.byteLength);
|
||||
|
||||
this._data = newData;
|
||||
},
|
||||
"+": function(other) {
|
||||
if (this.getType() !== other.getType()) {
|
||||
throw new Error("An attempt to add an " + other.className + " to " + this.className);
|
||||
}
|
||||
|
||||
var newData = new ArrayBuffer(this._data.byteLength + other._data.byteLength);
|
||||
var newView = new Uint8Array(newData);
|
||||
var thisView = new Uint8Array(this._data);
|
||||
var otherView = new Uint8Array(other._data);
|
||||
|
||||
newView.set(thisView, 0);
|
||||
newView.set(otherView, this._data.byteLength);
|
||||
|
||||
return new Blob(newData);
|
||||
},
|
||||
"base64": function() {
|
||||
var arr = new Uint8Array(this._data);
|
||||
var bin = "";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue