Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
229 lines
6.9 KiB
JavaScript
Executable file
229 lines
6.9 KiB
JavaScript
Executable file
/* global Service */
|
|
'use strict';
|
|
|
|
function LockScreenMediaPlaybackWidget(container, options) {
|
|
this.container = container;
|
|
this.nowPlaying = container.querySelector('.media-playback-nowplaying');
|
|
this.controls = container.querySelector('.media-playback-controls');
|
|
|
|
this.track = container.querySelector('.track');
|
|
|
|
this.previousButton = container.querySelector('.previous');
|
|
this.playPauseButton = container.querySelector('.play-pause');
|
|
this.nextButton = container.querySelector('.next');
|
|
|
|
this.previousButton.addEventListener(
|
|
'contextmenu', this.handleContextmenu.bind(this)
|
|
);
|
|
this.nextButton.addEventListener(
|
|
'contextmenu', this.handleContextmenu.bind(this)
|
|
);
|
|
this.container.addEventListener('touchend', this.handleTouchend.bind(this));
|
|
|
|
// When prev/next button releases, we need to know user simply clicking on it
|
|
// or holding on and perform fastseeking. Keeping this state to know which
|
|
// command should issue.
|
|
this.isFastSeeking = false;
|
|
|
|
window.addEventListener('iac-mediacomms', this.handleMessage.bind(this));
|
|
// When SCO status changes, we need to adjust the ui of the playback controls
|
|
window.addEventListener(
|
|
'bluetoothprofileconnectionchange', this.handleSCOChange.bind(this)
|
|
);
|
|
|
|
if (options && options.nowPlayingAction === 'openapp') {
|
|
this.nowPlaying.addEventListener('click', this.openMediaApp.bind(this));
|
|
}
|
|
|
|
// Listen for when the music app is terminated. We know which app to look
|
|
// for because we got it from the "appinfo" message. Then we hide the Now
|
|
// Playing container. XXX: This is a gigantic hack, stemming from
|
|
// <https://bugzilla.mozilla.org/show_bug.cgi?id=915880>. If you're thinking
|
|
// about doing something similar, step away from your keyboard immediately.
|
|
window.addEventListener('appterminated', function(event) {
|
|
if (event.detail.origin === this.origin) {
|
|
this.hidden = true;
|
|
}
|
|
}.bind(this));
|
|
|
|
// Requesting the current data (if any)
|
|
this.sendCommandEvent('updateplaystatus');
|
|
this.sendCommandEvent('updatemetadata');
|
|
}
|
|
|
|
LockScreenMediaPlaybackWidget.prototype = {
|
|
get hidden() {
|
|
return this.container.hidden;
|
|
},
|
|
|
|
set hidden(value) {
|
|
this.container.hidden = value;
|
|
},
|
|
|
|
handleMessage: function mpw_handleMessage(event) {
|
|
var message = event.detail;
|
|
switch (message.type) {
|
|
case 'appinfo':
|
|
this.updateAppInfo(message.data);
|
|
break;
|
|
case 'nowplaying':
|
|
this.updateNowPlaying(message.data);
|
|
break;
|
|
case 'status':
|
|
this.updatePlaybackStatus(message.data);
|
|
break;
|
|
}
|
|
},
|
|
|
|
handleSCOChange: function mpw_handleSCOChange(event) {
|
|
var name = event.detail.name;
|
|
var connected = event.detail.connected;
|
|
|
|
if (name === 'sco') {
|
|
this.container.classList.toggle('disabled', connected);
|
|
}
|
|
},
|
|
|
|
updateAppInfo: function mpw_updateAppInfo(info) {
|
|
if (!info) {
|
|
return;
|
|
}
|
|
|
|
this.origin = info.origin;
|
|
},
|
|
|
|
updateNowPlaying: function mpw_updateNowPlaying(metadata) {
|
|
if (!metadata) {
|
|
return;
|
|
}
|
|
|
|
var title = metadata.title.trim();
|
|
var artist = metadata.artist.trim();
|
|
|
|
if (title || artist) {
|
|
this.track.removeAttribute('data-l10n-id');
|
|
this.track.textContent= '';
|
|
|
|
if (title) {
|
|
var titleNode = document.createTextNode(title);
|
|
this.track.appendChild(titleNode);
|
|
}
|
|
|
|
if (title && artist) {
|
|
var emDashNode = document.createTextNode(' — '); // Using a —
|
|
var artistNode = document.createTextNode(artist);
|
|
var artistBdiNode = document.createElement('bdi');
|
|
this.track.appendChild(emDashNode);
|
|
artistBdiNode.appendChild(artistNode);
|
|
this.track.appendChild(artistBdiNode);
|
|
}
|
|
else if (artist) { // but no title
|
|
this.track.appendChild(document.createTextNode(artist));
|
|
}
|
|
} else {
|
|
this.track.setAttribute('data-l10n-id', 'UnknownTrack');
|
|
}
|
|
},
|
|
|
|
updatePlaybackStatus: function mp_updatePlaybackStatus(status) {
|
|
switch (status.playStatus) {
|
|
case 'PLAYING':
|
|
// WORKAROUND: Sometimes we can still get the playing event after music
|
|
// app terminated, so we check the music app existence when playback is
|
|
// hidden initially before showing. Remove it once Bug 915880 fixed.
|
|
var origin = this.origin;
|
|
if (this.hidden && !Service.query('AppWindowManager.getApp', origin)) {
|
|
return;
|
|
}
|
|
|
|
this.hidden = false;
|
|
this.playPauseButton.dataset.icon = 'pause';
|
|
this.playPauseButton.setAttribute('data-l10n-id', 'mediaPlaybackPause');
|
|
break;
|
|
case 'PAUSED':
|
|
this.hidden = false;
|
|
this.playPauseButton.dataset.icon = 'play';
|
|
this.playPauseButton.setAttribute('data-l10n-id', 'mediaPlaybackPlay');
|
|
break;
|
|
case 'STOPPED':
|
|
this.hidden = true;
|
|
break;
|
|
case 'mozinterruptbegin':
|
|
this.hidden = true;
|
|
break;
|
|
}
|
|
},
|
|
|
|
openMediaApp: function mp_openMediaApp(event) {
|
|
if (this.origin) {
|
|
var evt = new CustomEvent('displayapp', {
|
|
bubbles: true,
|
|
cancelable: true,
|
|
detail: Service.query('AppWindowManager.getApp', this.origin)
|
|
});
|
|
window.dispatchEvent(evt);
|
|
}
|
|
},
|
|
|
|
sendCommandEvent: function mp_sendCommandEvent(command) {
|
|
// XXX: Before we implement LockScreen as a standalone app,
|
|
// we make it inside a iframe, which would block this function
|
|
// to get the IAC port. So these commands need to be send outside
|
|
// the frame to System app, and System app would forward them via IAC.
|
|
window.dispatchEvent(new CustomEvent(
|
|
'lockscreen-request-mediacommand', {
|
|
detail: command
|
|
}));
|
|
},
|
|
|
|
handleContextmenu: function mp_handleContextmenu(event) {
|
|
var command = null;
|
|
|
|
switch (event.target) {
|
|
case this.previousButton:
|
|
command = 'rewindstart';
|
|
break;
|
|
case this.nextButton:
|
|
command = 'fastforwardstart';
|
|
break;
|
|
}
|
|
|
|
if (command) {
|
|
this.isFastSeeking = true;
|
|
this.sendCommandEvent(command);
|
|
}
|
|
},
|
|
|
|
handleTouchend: function mp_handleTouchend(event) {
|
|
var command = null;
|
|
|
|
switch (event.target) {
|
|
case this.previousButton:
|
|
if (this.isFastSeeking) {
|
|
this.isFastSeeking = false;
|
|
command = 'rewindend';
|
|
} else {
|
|
command = 'prevtrack';
|
|
}
|
|
break;
|
|
case this.nextButton:
|
|
if (this.isFastSeeking) {
|
|
this.isFastSeeking = false;
|
|
command = 'fastforwardend';
|
|
} else {
|
|
command = 'nexttrack';
|
|
}
|
|
break;
|
|
case this.playPauseButton:
|
|
// The play/pause indicator will get set once the music app replies
|
|
// with its "mode" message, but this will make us appear speedier.
|
|
event.target.classList.toggle('is-paused');
|
|
command = 'playpause';
|
|
break;
|
|
}
|
|
|
|
if (command) {
|
|
this.sendCommandEvent(command);
|
|
}
|
|
}
|
|
};
|