seeking, autonext song

This commit is contained in:
Blue 2019-01-26 21:54:22 +03:00
parent e690d67b80
commit 6e933257b1
7 changed files with 294 additions and 66 deletions

View File

@ -40,6 +40,6 @@ void M::Audio::initAdditional(const W::String& p_mime)
} }
} }
additional.insert(u"duration", new W::Uint64(length / MAD_TIMER_RESOLUTION)); additional.insert(u"duration", new W::Uint64(length * 1000 / MAD_TIMER_RESOLUTION));
additional.insert(u"bitrate", new W::Uint64(tBits / amount)); additional.insert(u"bitrate", new W::Uint64(tBits / amount));
} }

View File

@ -65,5 +65,40 @@ global.W = {
// Return the modified object // Return the modified object
return lTarget; return lTarget;
},
touchToMouse: function (e) {
e.preventDefault();
if (e.touches.length > 1 || (e.type == "touchend" && e.touches.length > 0))
return;
var type = null;
var touch = null;
var src = e.currentTarget;
switch (e.type) {
case "touchstart":
type = "mousedown";
touch = e.changedTouches[0];
break;
case "touchmove":
type = "mousemove";
touch = e.changedTouches[0];
src = window;
break;
case "touchend":
type = "mouseup";
touch = e.changedTouches[0];
src = window;
break;
}
var event = new MouseEvent(type, {
button: 0,
screenX: touch.screenX,
screenY: touch.screenY,
clientX: touch.clientX,
clientY: touch.clientY
});
src.dispatchEvent(event);
} }
}; };

View File

@ -17,7 +17,7 @@ var Audio = File.inherit({
return this._additional.at("bitrate").valueOf(); return this._additional.at("bitrate").valueOf();
}, },
getDuration: function() { getDuration: function() {
return this._additional.at("duration").valueOf(); return this._additional.at("duration").valueOf() / 1000;
} }
}); });

View File

@ -22,6 +22,9 @@ var Player = Controller.inherit({
this.views = Object.create(null); this.views = Object.create(null);
this.mode = PlayerMode.straight.playback; this.mode = PlayerMode.straight.playback;
this.progress = new ProgressModel(); this.progress = new ProgressModel();
this.progress.on("seekingStart", this._onSeekingStart, this);
this.progress.on("seekingEnd", this._onSeekingEnd, this);
this.progress.on("seek", this._onSeek, this);
this._audio = null; this._audio = null;
this._createStateMachine(); this._createStateMachine();
this._createPlayingInfrastructure(); this._createPlayingInfrastructure();
@ -31,7 +34,7 @@ var Player = Controller.inherit({
this.addHandler("play"); this.addHandler("play");
this.addHandler("pause"); this.addHandler("pause");
this._playbackInterval = setInterval(this._onInterval.bind(this), 250); this._playbackInterval = setInterval(this._onInterval.bind(this), intervalPrecision);
}, },
destructor: function() { destructor: function() {
this._clearInterval(this._playbackInterval); this._clearInterval(this._playbackInterval);
@ -82,8 +85,8 @@ var Player = Controller.inherit({
break; break;
case ItemType.straight.currentPlayback: case ItemType.straight.currentPlayback:
ctrl = new Vocabulary(address.clone()); ctrl = new Vocabulary(address.clone());
ctrl.on("newElement", this._onNewPlayBackElement, this); ctrl.on("newElement", this._onNewPlaybackElement, this);
ctrl.on("removeElement", this._onNewRemoveBackElement, this); ctrl.on("removeElement", this._onRemovePlaybackElement, this);
supported = true; supported = true;
break; break;
case ItemType.straight.picture: case ItemType.straight.picture:
@ -109,20 +112,34 @@ var Player = Controller.inherit({
this.trigger("newElement", ctrl, t); this.trigger("newElement", ctrl, t);
} }
}, },
_checkIfEnough: function() {
var diff = this._currentTime - this._seekingTime - this._ctx.currentTime;
if (diff > threshold) {
this._fsm.manipulation("enough");
} else {
this._fsm.manipulation("notEnough");
}
},
_createPlayingInfrastructure: function() { _createPlayingInfrastructure: function() {
this._ctx = new AudioContext(); this._ctx = new AudioContext();
this._decoder = new Mp3Decoder(); this._decoder = new Mp3Decoder();
this._currentTime = 0; this._currentTime = 0;
this._seekingTime = 0;
this._buffers = [];
this._sources = [];
this._ctx.suspend(); this._ctx.suspend();
}, },
_createStateMachine: function() {
this._fsm = new StateMachine("initial", graphs[this.mode]);
this._fsm.on("stateChanged", this._onStateChanged, this);
},
_destroyPlayingInfrastructure: function() { _destroyPlayingInfrastructure: function() {
this._ctx.close(); this._ctx.close();
this._decoder.delete(); this._decoder.delete();
}, },
_createStateMachine: function() { getCurrentPlaybackTime: function() {
this._fsm = new StateMachine("initial", graphs[this.mode]); return this._ctx.currentTime + this._seekingTime;
this._fsm.on("stateChanged", this._onStateChanged, this);
}, },
_h_get: function(ev) { _h_get: function(ev) {
var data = ev.getData(); var data = ev.getData();
@ -187,10 +204,26 @@ var Player = Controller.inherit({
if (sb === undefined) { if (sb === undefined) {
break; break;
} else { } else {
var src = this._ctx.createBufferSource(); this._buffers.push(sb);
src.buffer = sb;
src.connect(this._ctx.destination); var startTime = this._currentTime - this._seekingTime;
src.start(this._currentTime); if (startTime < this._ctx.currentTime) {
var offset = startTime - this._ctx.currentTime + sb.duration;
if (offset > 0) {
var src = this._ctx.createBufferSource();
src.buffer = sb;
src.connect(this._ctx.destination);
src.start(0, Math.abs(startTime - this._ctx.currentTime));
this._sources.push(src);
}
} else {
var src = this._ctx.createBufferSource();
src.buffer = sb;
src.connect(this._ctx.destination);
src.start(startTime);
this._sources.push(src);
}
this._currentTime += sb.duration; this._currentTime += sb.duration;
} }
} }
@ -208,11 +241,22 @@ var Player = Controller.inherit({
this._fsm.manipulation("controllerReady"); this._fsm.manipulation("controllerReady");
}, },
_onInterval: function() { _onInterval: function() {
if (this._audio && this._audio.initialized) { if (this._audio && this._audio.initialized && seekingStates.indexOf(this._fsm.state()) === -1) {
this.progress.setPlayback(this._ctx.currentTime / this._audio.getDuration()); var duration = this._audio.getDuration();
this.progress.setPlayback(this.getCurrentPlaybackTime() / duration);
this._checkIfEnough();
if (this.progress.playback >= 0.9999) {
var next = this.controls[ItemType.straight.next];
if (next && next.enabled) {
next.activate();
} else {
//todo kinda stop state?
}
}
} }
}, },
_onNewPlayBackElement: function(key, element) { _onNewPlaybackElement: function(key, element) {
switch (key) { switch (key) {
case "image": case "image":
var address = new Address(["images", element.toString()]); var address = new Address(["images", element.toString()]);
@ -230,7 +274,7 @@ var Player = Controller.inherit({
break; break;
} }
}, },
_onNewRemoveBackElement: function(key) { _onRemovePlaybackElement: function(key) {
switch (key) { switch (key) {
case "image": case "image":
this._removeView(ItemType.straight.picture); this._removeView(ItemType.straight.picture);
@ -241,6 +285,53 @@ var Player = Controller.inherit({
this._audio = null; this._audio = null;
} }
}, },
_onSeek: function(progress) {
if (seekingStates.indexOf(this._fsm.state()) !== -1) {
this.progress.setPlayback(progress);
}
},
_onSeekingStart: function() {
this._fsm.manipulation("startSeeking");
},
_onSeekingEnd: function(progress) {
if (seekingStates.indexOf(this._fsm.state()) !== -1) {
for (var i = 0; i < this._sources.length; ++i) {
this._sources[i].stop();
}
this._sources = [];
var ct = this.getCurrentPlaybackTime();
var duration = this._audio.getDuration();
var targetTime = duration * progress;
this._seekingTime += targetTime - ct;
var nc = 0;
for (var i = 0; i < this._buffers.length; ++i) {
var buffer = this._buffers[i];
var startTime = nc - targetTime;
if (startTime < 0) {
var offset = startTime + buffer.duration;
if (offset > 0) {
var src = this._ctx.createBufferSource();
src.buffer = buffer;
src.connect(this._ctx.destination);
src.start(0, Math.abs(startTime));
this._sources.push(src);
}
} else {
var src = this._ctx.createBufferSource();
src.buffer = buffer;
src.connect(this._ctx.destination);
src.start(this._ctx.currentTime + startTime);
this._sources.push(src);
}
nc += buffer.duration;
}
}
this._fsm.manipulation("stopSeeking");
},
_onStateChanged: function(e) { _onStateChanged: function(e) {
switch (e.newState) { switch (e.newState) {
case "initial": case "initial":
@ -273,12 +364,31 @@ var Player = Controller.inherit({
case "hasControllerPlaying": case "hasControllerPlaying":
if (this._audio.hasMore()) { if (this._audio.hasMore()) {
this._audio.requestSlice(audioPortion); this._audio.requestSlice(audioPortion);
this._ctx.resume(); //todo temporal
} else { } else {
this._fsm.manipulation("noMoreFrames"); this._fsm.manipulation("noMoreFrames");
} }
break; break;
case "buffering":
break;
case "bufferingPlaying":
if (e.oldState === "playing") {
this._ctx.suspend();
}
break;
case "seeking":
break;
case "seekingPlaying":
if (e.oldState === "playing") {
this._ctx.suspend();
}
break;
case "seekingAllLoaded":
break;
case "seekingPlayingAllLoaded":
if (e.oldState === "playingAllLoaded") {
this._ctx.suspend();
}
break;
case "paused": case "paused":
switch (e.oldState) { switch (e.oldState) {
case "playing": case "playing":
@ -299,6 +409,8 @@ var Player = Controller.inherit({
case "playingAllLoaded": case "playingAllLoaded":
switch (e.oldState) { switch (e.oldState) {
case "pausedAllLoaded": case "pausedAllLoaded":
case "bufferingPlaying":
case "seekingPlayingAllLoaded":
this._ctx.resume(); this._ctx.resume();
break; break;
} }
@ -365,36 +477,77 @@ graphs[PlayerMode.straight.playback] = {
controllerReady: "hasControllerPlaying" controllerReady: "hasControllerPlaying"
}, },
"hasController": { "hasController": {
newFrames: "paused", newFrames: "bufferingPlaying",
play: "hasControllerPlaying", play: "hasControllerPlaying",
noController: "initial" noController: "initial"
}, },
"hasControllerPlaying": { "hasControllerPlaying": {
newFrames: "playing", newFrames: "bufferingPlaying",
pause: "hasController", pause: "hasController",
noController: "initialPlaying" noController: "initialPlaying"
}, },
"buffering": {
play: "bufferingPlaying",
enough: "paused",
noMoreFrames: "pausedAllLoaded",
startSeeking: "seeking",
noController: "initial"
},
"bufferingPlaying": {
pause: "buffering",
enough: "playing",
noMoreFrames: "playingAllLoaded",
startSeeking: "seekingPlaying",
noController: "initialPlaying"
},
"seeking": {
stopSeeking: "buffering",
noMoreFrames: "seekingAllLoaded",
noController: "initial"
},
"seekingPlaying": {
stopSeeking: "bufferingPlaying",
noMoreFrames: "playingAllLoaded",
noController: "initialPlaying"
},
"seekingAllLoaded": {
stopSeeking: "pausedAllLoaded",
noController: "initial"
},
"seekingPlayingAllLoaded": {
stopSeeking: "playingAllLoaded",
noController: "initialPlaying"
},
"paused": { "paused": {
play: "playing", play: "playing",
notEnough: "buffering",
noController: "initial", noController: "initial",
noMoreFrames: "pausedAllLoaded" noMoreFrames: "pausedAllLoaded",
startSeeking: "seeking"
}, },
"pausedAllLoaded": { "pausedAllLoaded": {
play: "playingAllLoaded", play: "playingAllLoaded",
noController: "initial" noController: "initial",
startSeeking: "seekingAllLoaded"
}, },
"playing": { "playing": {
pause: "paused", pause: "paused",
notEnough: "bufferingPlaying",
noMoreFrames: "playingAllLoaded", noMoreFrames: "playingAllLoaded",
noController: "initialPlaying" noController: "initialPlaying",
startSeeking: "seekingPlaying"
}, },
"playingAllLoaded": { "playingAllLoaded": {
pause: "pausedAllLoaded", pause: "pausedAllLoaded",
noController: "initialPlaying" noController: "initialPlaying",
startSeeking: "seekingPlayingAllLoaded"
} }
} }
var seekingStates = ["seeking", "seekingPlaying", "seekingAllLoaded", "seekingPlayingAllLoaded"]
var audioPortion = 1024 * 50; var audioPortion = 1024 * 50; //bytes to download for each portion
var threshold = 2; //seconds to buffer before playing
var intervalPrecision = 100; //millisecond of how often to check the playback
var ProgressModel = Model.inherit({ var ProgressModel = Model.inherit({
className: "ProgressModel", className: "ProgressModel",

View File

@ -30,9 +30,9 @@
this._y = 0; this._y = 0;
this._e.addEventListener("mousedown", this._proxy.onMouseDown, false); this._e.addEventListener("mousedown", this._proxy.onMouseDown, false);
this._e.addEventListener("touchstart", this._touch, false); this._e.addEventListener("touchstart", W.touchToMouse, false);
this._e.addEventListener("touchmove", this._touch, false); this._e.addEventListener("touchmove", W.touchToMouse, false);
this._e.addEventListener("touchend", this._touch, false); this._e.addEventListener("touchend", W.touchToMouse, false);
}, },
"destructor": function () { "destructor": function () {
if (this._dragging) { if (this._dragging) {
@ -45,9 +45,9 @@
} }
this._e.removeEventListener("mousedown", this._proxy.onMouseDown); this._e.removeEventListener("mousedown", this._proxy.onMouseDown);
this._e.removeEventListener("touchstart", this._touch); this._e.removeEventListener("touchstart", W.touchToMouse);
this._e.removeEventListener("touchmove", this._touch); this._e.removeEventListener("touchmove", W.touchToMouse);
this._e.removeEventListener("touchend", this._touch); this._e.removeEventListener("touchend", W.touchToMouse);
Subscribable.fn.destructor.call(this); Subscribable.fn.destructor.call(this);
}, },
@ -105,41 +105,6 @@
this._v.trigger("dragEnd"); this._v.trigger("dragEnd");
return false; return false;
} }
},
"_touch": function (e) {
e.preventDefault();
if (e.touches.length > 1 || (e.type == "touchend" && e.touches.length > 0))
return;
var type = null;
var touch = null;
var src = e.currentTarget;
switch (e.type) {
case "touchstart":
type = "mousedown";
touch = e.changedTouches[0];
break;
case "touchmove":
type = "mousemove";
touch = e.changedTouches[0];
src = window;
break;
case "touchend":
type = "mouseup";
touch = e.changedTouches[0];
src = window;
break;
}
var event = new MouseEvent(type, {
button: 0,
screenX: touch.screenX,
screenY: touch.screenY,
clientX: touch.clientX,
clientY: touch.clientY
});
src.dispatchEvent(event);
} }
}); });

View File

@ -20,14 +20,32 @@
this._createBars(); this._createBars();
View.fn.constructor.call(this, controller, base, el); View.fn.constructor.call(this, controller, base, el);
this._seeking = false;
this._createProxy();
this._f.on("load", this._onLoad, this); this._f.on("load", this._onLoad, this);
this._f.on("playback", this._onPlayback, this); this._f.on("playback", this._onPlayback, this);
this._e.style.backgroundColor = "#eeeeee"; this._e.style.backgroundColor = "#eeeeee";
this._e.appendChild(this._loadProgress); this._e.appendChild(this._loadProgress);
this._e.appendChild(this._playbackProgress); this._e.appendChild(this._playbackProgress);
this._e.addEventListener("mousedown", this._proxy.onMouseDown, false);
this._e.addEventListener("touchstart", W.touchToMouse, false);
this._e.addEventListener("touchmove", W.touchToMouse, false);
this._e.addEventListener("touchend", W.touchToMouse, false);
}, },
destructor: function() { destructor: function() {
if (this._seeking) {
window.removeEventListener("mouseup", this._proxy.onMouseUp);
window.removeEventListener("mousemove", this._proxy.onMouseMove);
}
this._e.removeEventListener("mousedown", this._proxy.onMouseDown);
this._e.removeEventListener("touchstart", W.touchToMouse);
this._e.removeEventListener("touchmove", W.touchToMouse);
this._e.removeEventListener("touchend", W.touchToMouse);
this._f.off("load", this._onLoad, this); this._f.off("load", this._onLoad, this);
this._f.off("playback", this._onPlayback, this); this._f.off("playback", this._onPlayback, this);
@ -52,6 +70,13 @@
this._playbackProgress.style.top = "0"; this._playbackProgress.style.top = "0";
this._playbackProgress.style.left = "0"; this._playbackProgress.style.left = "0";
}, },
_createProxy: function () {
this._proxy = {
onMouseDown: this._onMouseDown.bind(this),
onMouseUp: this._onMouseUp.bind(this),
onMouseMove: this._onMouseMove.bind(this)
}
},
_onData: function() { _onData: function() {
this._onLoad(this._f.load); this._onLoad(this._f.load);
this._onPlayback(this._f.playback); this._onPlayback(this._f.playback);
@ -59,6 +84,40 @@
_onLoad: function(load) { _onLoad: function(load) {
this._loadProgress.style.width = load * 100 + "%"; this._loadProgress.style.width = load * 100 + "%";
}, },
_onMouseDown: function(e) {
if (e.which === 1) {
window.addEventListener("mouseup", this._proxy.onMouseUp);
window.addEventListener("mousemove", this._proxy.onMouseMove);
this._seeking = true;
this._f.trigger("seekingStart");
this._ap = this.getAbsolutePosition();
var seek = Math.max(Math.min(e.pageX - this._ap.x, this._w), 0);
var nSeek = seek / this._w;
if (this._seek !== nSeek) {
this._seek = nSeek;
this._f.trigger("seek", this._seek);
}
}
},
_onMouseMove: function(e) {
var seek = Math.max(Math.min(e.pageX - this._ap.x, this._w), 0);
var nSeek = seek / this._w;
if (this._seek !== nSeek) {
this._seek = nSeek;
this._f.trigger("seek", this._seek);
}
},
_onMouseUp: function() {
delete this._ap;
delete this._seek;
this._f.trigger("seekingEnd", this._f.playback);
this._seeking = false;
window.removeEventListener("mouseup", this._proxy.onMouseUp);
window.removeEventListener("mousemove", this._proxy.onMouseMove);
},
_onPlayback: function(pb) { _onPlayback: function(pb) {
this._playbackProgress.style.width = pb * 100 + "%"; this._playbackProgress.style.width = pb * 100 + "%";
}, },

View File

@ -98,6 +98,21 @@
return w; return w;
}, },
"getAbsolutePosition": function() {
var pp;
if (this._p) {
pp = this._p.getAbsolutePosition();
} else {
pp = Object.create(null);
pp.x = 0;
pp.y = 0;
}
pp.x += this._x;
pp.y += this._y;
return pp;
},
"_initDraggable": function() { "_initDraggable": function() {
this._dg = new Draggable(this, { this._dg = new Draggable(this, {
snapDistance: this._o.snapDistance snapDistance: this._o.snapDistance
@ -134,6 +149,7 @@
"remove": function() { "remove": function() {
if (this._p) { if (this._p) {
this._p.removeChild(this); this._p.removeChild(this);
delete this._p; //just to make sure
} }
}, },
"removeClass": function(className) { "removeClass": function(className) {