Resolve "for chat mode, auto-play next audio message"
This commit is contained in:
parent
f49d374a76
commit
daa52be9c0
11 changed files with 455 additions and 252 deletions
233
src/services/audio.service.js
Normal file
233
src/services/audio.service.js
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
import utils from "../plugins/utils";
|
||||
|
||||
/**
|
||||
* This plugin (available in all vue components as $audioPlayer) handles
|
||||
* access to the shared audio player, and events related to loading and
|
||||
* playback of audio attachments.
|
||||
*
|
||||
* Components use this by calling "addListener" (and corresponding removeListener) with
|
||||
* an audio matrix event and a unique component id (for example the ._uid property).
|
||||
*/
|
||||
export default {
|
||||
install(Vue) {
|
||||
class SharedAudioPlayer {
|
||||
constructor() {
|
||||
this.player = new Audio();
|
||||
this.currentEvent = null;
|
||||
this.infoMap = new Map();
|
||||
this.player.addEventListener("durationchange", this.onDurationChange.bind(this));
|
||||
this.player.addEventListener("timeupdate", this.onTimeUpdate.bind(this));
|
||||
this.player.addEventListener("play", this.onPlay.bind(this));
|
||||
this.player.addEventListener("pause", this.onPause.bind(this));
|
||||
this.player.addEventListener("ended", this.onEnded.bind(this));
|
||||
}
|
||||
|
||||
getPlayerElement() {
|
||||
return this.player;
|
||||
}
|
||||
|
||||
addListener(uid, event) {
|
||||
const eventId = event.getId();
|
||||
var entry = this.infoMap.get(eventId);
|
||||
if (!entry) {
|
||||
// Listeners is just a Set of component "uid" entries for now.
|
||||
entry = { url: null, listeners: new Set() };
|
||||
// Make these reactive, so AudioPlayer (and others) can listen to them
|
||||
Vue.set(entry, "loading", false);
|
||||
Vue.set(entry, "loadPercent", 0);
|
||||
Vue.set(entry, "duration", 0);
|
||||
Vue.set(entry, "currentTime", 0);
|
||||
Vue.set(entry, "playPercent", 0);
|
||||
Vue.set(entry, "playing", false);
|
||||
this.infoMap.set(eventId, entry);
|
||||
|
||||
// Get duration information
|
||||
utils
|
||||
.getAttachmentUrlAndDuration(event)
|
||||
.then(([ignoredurl, duration]) => {
|
||||
entry.duration = duration;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Failed to fetch attachment duration: ", err);
|
||||
});
|
||||
}
|
||||
entry.listeners.add(uid);
|
||||
return entry;
|
||||
}
|
||||
removeListener(uid) {
|
||||
[...this.infoMap].forEach(([ignoredeventid, info]) => {
|
||||
info.listeners.delete(uid);
|
||||
if (info.listeners.size == 0 && info.url) {
|
||||
// No more listeners, release audio blob
|
||||
URL.revokeObjectURL(info.url);
|
||||
info.url = null;
|
||||
}
|
||||
});
|
||||
this.infoMap = new Map(
|
||||
[...this.infoMap].filter(([ignoredeventid, info]) => {
|
||||
return info.listeners.size > 0;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
play(event) {
|
||||
this.play_(event, false);
|
||||
}
|
||||
|
||||
load(event) {
|
||||
this.play_(event, true);
|
||||
}
|
||||
|
||||
play_(event, onlyLoad) {
|
||||
const eventId = event.getId();
|
||||
if (this.currentEvent != eventId) {
|
||||
// Media change, pause the one currently playing.
|
||||
this.player.pause();
|
||||
var entry = this.infoMap.get(this.currentEvent);
|
||||
if (entry) {
|
||||
entry.playing = false;
|
||||
}
|
||||
}
|
||||
this.currentEvent = eventId;
|
||||
const info = this.infoMap.get(eventId);
|
||||
if (info) {
|
||||
if (info.url) {
|
||||
// Restart from beginning?
|
||||
if (info.currentTime == info.duration) {
|
||||
info.currentTime = 0;
|
||||
info.playPercent = 0;
|
||||
}
|
||||
if (this.player.src != info.url) {
|
||||
this.player.src = info.url;
|
||||
this.player.currentTime = (info.currentTime || 0) / 1000;
|
||||
}
|
||||
if (onlyLoad) {
|
||||
this.player.load();
|
||||
} else {
|
||||
this.player.play();
|
||||
}
|
||||
} else {
|
||||
// Download it!
|
||||
info.loadPercent = 0;
|
||||
info.loading = true;
|
||||
info.abortController = new AbortController();
|
||||
utils
|
||||
.getAttachment(this.$root.$matrix.matrixClient, event, (progress) => {
|
||||
info.loadPercent = progress;
|
||||
}, false, info.abortController)
|
||||
.then((url) => {
|
||||
info.url = url;
|
||||
|
||||
// Still on this item? Call ourselves recursively.
|
||||
if (this.currentEvent == eventId) {
|
||||
if (onlyLoad) {
|
||||
this.load(event);
|
||||
} else {
|
||||
this.play(event);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Failed to fetch attachment: ", err);
|
||||
})
|
||||
.finally(() => {
|
||||
info.loading = false;
|
||||
info.abortController = undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the "autoplay" property on the underlying player object.
|
||||
* @param {} autoplay
|
||||
*/
|
||||
setAutoplay(autoplay) {
|
||||
this.player.autoplay = autoplay;
|
||||
}
|
||||
|
||||
pause(event) {
|
||||
if (!event || this.currentEvent == event.getId()) {
|
||||
this.player.pause();
|
||||
}
|
||||
|
||||
if (event) {
|
||||
// If downloading, abort that!
|
||||
var entry = this.infoMap.get(event.getId());
|
||||
if (entry && entry.abortController) {
|
||||
entry.abortController.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
seek(event, percent) {
|
||||
var entry = this.infoMap.get(event.getId());
|
||||
if (entry) {
|
||||
entry.currentTime = ((percent / 100) * (entry.duration || 0));
|
||||
this.updatePlayPercent(entry);
|
||||
if (this.currentEvent == event.getId()) {
|
||||
this.player.currentTime = entry.currentTime / 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
seekRelative(event, milliseconds) {
|
||||
var entry = this.infoMap.get(event.getId());
|
||||
if (entry) {
|
||||
entry.currentTime = Math.max(0, Math.min(entry.currentTime + milliseconds, entry.duration));
|
||||
this.updatePlayPercent(entry);
|
||||
if (this.currentEvent == event.getId()) {
|
||||
this.player.currentTime = entry.currentTime / 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
onPlay() {
|
||||
var entry = this.infoMap.get(this.currentEvent);
|
||||
if (entry) {
|
||||
entry.playing = true;
|
||||
}
|
||||
this.$root.$emit("audio-playback-started", this.currentEvent);
|
||||
}
|
||||
onPause() {
|
||||
var entry = this.infoMap.get(this.currentEvent);
|
||||
if (entry) {
|
||||
entry.playing = false;
|
||||
}
|
||||
this.$root.$emit("audio-playback-paused", this.currentEvent);
|
||||
}
|
||||
onEnded() {
|
||||
var entry = this.infoMap.get(this.currentEvent);
|
||||
if (entry) {
|
||||
entry.playing = false;
|
||||
entry.currentTime = entry.duration; // Next time restart
|
||||
}
|
||||
this.$root.$emit("audio-playback-ended", this.currentEvent);
|
||||
}
|
||||
onTimeUpdate() {
|
||||
var entry = this.infoMap.get(this.currentEvent);
|
||||
if (entry) {
|
||||
entry.currentTime = 1000 * this.player.currentTime;
|
||||
this.updatePlayPercent(entry);
|
||||
}
|
||||
}
|
||||
onDurationChange() {
|
||||
const duration =
|
||||
this.player.duration && isFinite(this.player.duration) && !isNaN(this.player.duration)
|
||||
? 1000 * this.player.duration
|
||||
: 0;
|
||||
var entry = this.infoMap.get(this.currentEvent);
|
||||
if (entry) {
|
||||
entry.duration = duration;
|
||||
this.updatePlayPercent(entry);
|
||||
}
|
||||
}
|
||||
updatePlayPercent(entry) {
|
||||
if (entry.duration > 0) {
|
||||
entry.playPercent = Math.floor((100 / entry.duration) * entry.currentTime);
|
||||
} else {
|
||||
entry.playPercent = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vue.prototype.$audioPlayer = new SharedAudioPlayer();
|
||||
},
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue