276 lines
8.5 KiB
JavaScript
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);
|
|
},
|
|
});
|
|
|