platypush/platypush/backend/http/static/js/plugins/media/index.js

276 lines
8.5 KiB
JavaScript

// Will be filled by dynamically loading media type handler scripts
const MediaHandlers = {};
const mediaUtils = {
methods: {
convertTime: function(time) {
time = parseFloat(time); // Normalize strings
var t = {};
t.h = '' + parseInt(time/3600);
t.m = '' + parseInt(time/60 - t.h*60);
t.s = '' + parseInt(time - (t.h*3600 + t.m*60));
for (var attr of ['m','s']) {
if (parseInt(t[attr]) < 10) {
t[attr] = '0' + t[attr];
}
}
var ret = [];
if (parseInt(t.h)) {
ret.push(t.h);
}
ret.push(t.m, t.s);
return ret.join(':');
},
convertSize: function(size) {
size = parseInt(size); // Normalize strings
const units = ['B', 'KB', 'MB', 'GB'];
let s=size, i=0;
for (; s > 1024 && i < units.length; i++, s = parseInt(s/1024));
return (size / Math.pow(2, 10*i)).toFixed(2) + ' ' + units[i];
},
},
};
Vue.component('media', {
template: '#tmpl-media',
props: ['config','player'],
mixins: [mediaUtils],
data: function() {
return {
bus: new Vue({}),
results: [],
status: {},
selectedDevice: {},
loading: {
results: false,
media: false,
},
infoModal: {
visible: false,
loading: false,
item: {},
},
subsModal: {
visible: false,
},
};
},
computed: {
types: function() {
return MediaHandlers;
},
},
methods: {
onResultsLoading: function() {
this.loading.results = true;
},
onResultsReady: async function(results) {
for (const result of results) {
if (result.type && MediaHandlers[result.type]) {
result.handler = MediaHandlers[result.type];
} else {
result.type = 'generic';
result.handler = MediaHandlers.generic;
for (const [handlerType, handler] of Object.entries(MediaHandlers)) {
if (handler.matchesUrl && handler.matchesUrl(result.url)) {
result.type = handlerType;
result.handler = handler;
break;
}
}
}
Object.entries(await result.handler.getMetadata(result, onlyBase=true)).forEach(entry => {
Vue.set(result, entry[0], entry[1]);
});
}
this.results = results;
this.loading.results = false;
},
play: async function(item) {
if (!this.selectedDevice.accepts[item.type]) {
item = await this.startStreaming(item.url);
}
let status = await this.selectedDevice.play(item.url, item.subtitles);
this.subsModal.visible = false;
this.onStatusUpdate({
device: this.selectedDevice,
status: status,
});
},
pause: async function() {
let status = await this.selectedDevice.pause();
this.onStatusUpdate({
device: this.selectedDevice,
status: status,
});
},
stop: async function() {
let status = await this.selectedDevice.stop();
this.onStatusUpdate({
device: this.selectedDevice,
status: status,
});
},
seek: async function(position) {
let status = await this.selectedDevice.seek(position);
this.onStatusUpdate({
device: this.selectedDevice,
status: status,
});
},
setVolume: async function(volume) {
let status = await this.selectedDevice.setVolume(volume);
this.onStatusUpdate({
device: this.selectedDevice,
status: status,
});
},
info: function(item) {
for (const [attr, value] of Object.entries(item)) {
Vue.set(this.infoModal.item, attr, value);
}
this.infoModal.loading = false;
this.infoModal.visible = true;
},
infoLoading: function() {
this.infoModal.loading = true;
this.infoModal.visible = true;
},
startStreaming: async function(item) {
const resource = item instanceof Object ? item.url : item;
const ret = await request('media.start_streaming', {
media: resource,
});
this.bus.$emit('streaming-started', {
url: ret.url,
resource: resource,
});
return ret;
},
searchSubs: function(item) {
if (typeof item === 'string')
item = {url: item};
this.subsModal.visible = true;
this.$refs.subs.search(item);
},
selectDevice: async function(device) {
this.selectedDevice = device;
let status = await this.selectedDevice.status();
this.onStatusUpdate({
device: this.selectedDevice,
status: status,
});
},
syncPosition: function(status) {
status._syncTime = {
timestamp: new Date(),
position: status.position,
};
},
onStatusUpdate: function(event) {
const dev = event.device;
const status = event.status;
this.syncPosition(status);
if (!this.status[dev.type])
Vue.set(this.status, dev.type, {});
Vue.set(this.status[dev.type], dev.name, status);
},
onMediaEvent: async function(event) {
let status = await request(event.plugin + '.status');
this.syncPosition(status);
if (event.resource) {
event.url = event.resource;
delete event.resource;
}
if (event.plugin.startsWith('media.'))
event.plugin = event.plugin.substr(6);
if (this.status[event.player] && this.status[event.player][event.plugin])
Vue.set(this.status[event.player], event.plugin, status);
else if (this.status[event.plugin] && this.status[event.plugin][event.player])
Vue.set(this.status[event.plugin], event.player, status);
},
timerFunc: function() {
for (const [playerType, players] of Object.entries(this.status)) {
for (const [playerName, status] of Object.entries(players)) {
if (status.state === 'play' && !isNaN(status.position) && status._syncTime) {
status.position = status._syncTime.position +
((new Date()).getTime()/1000) - (status._syncTime.timestamp.getTime()/1000);
}
}
}
},
},
created: function() {
for (const [type, Handler] of Object.entries(MediaHandlers)) {
MediaHandlers[type] = new Handler();
MediaHandlers[type].bus = this.bus;
}
registerEventHandler(this.onMediaEvent,
'platypush.message.event.media.NewPlayingMediaEvent',
'platypush.message.event.media.MediaPlayEvent',
'platypush.message.event.media.MediaPauseEvent',
'platypush.message.event.media.MediaStopEvent',
'platypush.message.event.media.MediaSeekEvent');
this.bus.$on('play', this.play);
this.bus.$on('pause', this.pause);
this.bus.$on('stop', this.stop);
this.bus.$on('seek', this.seek);
this.bus.$on('volume', this.setVolume);
this.bus.$on('info', this.info);
this.bus.$on('info-loading', this.infoLoading);
this.bus.$on('selected-device', this.selectDevice);
this.bus.$on('results-loading', this.onResultsLoading);
this.bus.$on('results-ready', this.onResultsReady);
this.bus.$on('status-update', this.onStatusUpdate);
this.bus.$on('start-streaming', this.startStreaming);
this.bus.$on('search-subs', this.searchSubs);
setInterval(this.timerFunc, 1000);
},
});