-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(UI): Add remote button with RemotePlayback API (#5650)
API: https://developer.mozilla.org/en-US/docs/Web/API/RemotePlayback Chrome Status: https://chromestatus.com/feature/5778318691401728 Examples: https://avayvod.github.io/remote-playback/test.html and https://beaufortfrancois.github.io/sandbox/media/remote-playback.html Tested in Safari (iOS and macOS) and Chrome in Android About desktop support: https://bugs.chromium.org/p/chromium/issues/detail?id=728609
- Loading branch information
Showing
6 changed files
with
279 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
/*! @license | ||
* Shaka Player | ||
* Copyright 2016 Google LLC | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
/** | ||
* @fileoverview Externs for HTMLMediaElement which were missing in the | ||
* Closure compiler. | ||
* | ||
* @externs | ||
*/ | ||
|
||
|
||
/** | ||
* @constructor | ||
* @implements {EventTarget} | ||
*/ | ||
function RemotePlayback() {} | ||
|
||
|
||
/** | ||
* Represents the RemotePlayback connection's state. | ||
* @type {string} | ||
*/ | ||
RemotePlayback.prototype.state; | ||
|
||
|
||
/** | ||
* The watchAvailability() method of the RemotePlayback interface watches | ||
* the list of available remote playback devices and returns a Promise that | ||
* resolves with the callbackId of a remote playback device. | ||
* | ||
* @param {!function(boolean)} callback | ||
* @return {!Promise} | ||
*/ | ||
RemotePlayback.prototype.watchAvailability = function(callback) {}; | ||
|
||
|
||
/** | ||
* The cancelWatchAvailability() method of the RemotePlayback interface | ||
* cancels the request to watch for one or all available devices. | ||
* | ||
* @param {number} id | ||
* @return {!Promise} | ||
*/ | ||
RemotePlayback.prototype.cancelWatchAvailability = function(id) {}; | ||
|
||
|
||
/** | ||
* The prompt() method of the RemotePlayback interface prompts the user | ||
* to select an available remote playback device and give permission | ||
* for the current media to be played using that device. | ||
* | ||
* If the user gives permission, the state will be set to connecting and | ||
* the user agent will connect to the device to initiate playback. | ||
* | ||
* If the user chooses to instead disconnect from the device, the state will | ||
* be set to disconnected and user agent will disconnect from this device. | ||
* | ||
* @return {!Promise} | ||
*/ | ||
RemotePlayback.prototype.prompt = function() {}; | ||
|
||
|
||
/** @type {RemotePlayback} */ | ||
HTMLMediaElement.prototype.remote; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
/*! @license | ||
* Shaka Player | ||
* Copyright 2016 Google LLC | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
|
||
goog.provide('shaka.ui.RemoteButton'); | ||
|
||
goog.require('shaka.Player'); | ||
goog.require('shaka.ui.Controls'); | ||
goog.require('shaka.ui.Element'); | ||
goog.require('shaka.ui.Enums'); | ||
goog.require('shaka.ui.Locales'); | ||
goog.require('shaka.ui.Localization'); | ||
goog.require('shaka.ui.OverflowMenu'); | ||
goog.require('shaka.ui.Utils'); | ||
goog.require('shaka.util.Dom'); | ||
goog.require('shaka.util.Platform'); | ||
goog.requireType('shaka.ui.Controls'); | ||
|
||
|
||
/** | ||
* @extends {shaka.ui.Element} | ||
* @final | ||
* @export | ||
*/ | ||
shaka.ui.RemoteButton = class extends shaka.ui.Element { | ||
/** | ||
* @param {!HTMLElement} parent | ||
* @param {!shaka.ui.Controls} controls | ||
*/ | ||
constructor(parent, controls) { | ||
super(parent, controls); | ||
|
||
/** @private {!HTMLButtonElement} */ | ||
this.remoteButton_ = shaka.util.Dom.createButton(); | ||
this.remoteButton_.classList.add('shaka-remote-button'); | ||
this.remoteButton_.classList.add('shaka-tooltip'); | ||
this.remoteButton_.ariaPressed = 'false'; | ||
|
||
/** @private {!HTMLElement} */ | ||
this.remoteIcon_ = shaka.util.Dom.createHTMLElement('i'); | ||
this.remoteIcon_.classList.add('material-icons-round'); | ||
let icon = shaka.ui.Enums.MaterialDesignIcons.CAST; | ||
const safariVersion = shaka.util.Platform.safariVersion(); | ||
if (safariVersion && safariVersion >= 13) { | ||
icon = shaka.ui.Enums.MaterialDesignIcons.AIRPLAY; | ||
} | ||
this.remoteIcon_.textContent = icon; | ||
this.remoteButton_.appendChild(this.remoteIcon_); | ||
|
||
const label = shaka.util.Dom.createHTMLElement('label'); | ||
label.classList.add('shaka-overflow-button-label'); | ||
label.classList.add('shaka-overflow-menu-only'); | ||
this.remoteNameSpan_ = shaka.util.Dom.createHTMLElement('span'); | ||
label.appendChild(this.remoteNameSpan_); | ||
|
||
this.remoteCurrentSelectionSpan_ = | ||
shaka.util.Dom.createHTMLElement('span'); | ||
this.remoteCurrentSelectionSpan_.classList.add( | ||
'shaka-current-selection-span'); | ||
label.appendChild(this.remoteCurrentSelectionSpan_); | ||
this.remoteButton_.appendChild(label); | ||
this.parent.appendChild(this.remoteButton_); | ||
|
||
/** @private {number} */ | ||
this.callbackId_ = -1; | ||
|
||
// Setup strings in the correct language | ||
this.updateLocalizedStrings_(); | ||
|
||
shaka.ui.Utils.setDisplay(this.remoteButton_, false); | ||
|
||
if (!this.video.remote || this.video.disableRemotePlayback) { | ||
this.remoteButton_.classList.add('shaka-hidden'); | ||
} else { | ||
this.eventManager.listen( | ||
this.localization, shaka.ui.Localization.LOCALE_UPDATED, () => { | ||
this.updateLocalizedStrings_(); | ||
}); | ||
|
||
this.eventManager.listen( | ||
this.localization, shaka.ui.Localization.LOCALE_CHANGED, () => { | ||
this.updateLocalizedStrings_(); | ||
}); | ||
|
||
this.eventManager.listen(this.controls, 'caststatuschanged', () => { | ||
this.updateRemoteState_(); | ||
}); | ||
|
||
this.eventManager.listen(this.remoteButton_, 'click', () => { | ||
this.video.remote.prompt(); | ||
}); | ||
|
||
this.eventManager.listen(this.video.remote, 'connect', () => { | ||
this.updateRemoteState_(); | ||
}); | ||
|
||
this.eventManager.listen(this.video.remote, 'connecting', () => { | ||
this.updateRemoteState_(); | ||
}); | ||
|
||
this.eventManager.listen(this.video.remote, 'disconnect', () => { | ||
this.updateRemoteState_(); | ||
}); | ||
|
||
this.eventManager.listen(this.player, 'loaded', () => { | ||
this.updateRemoteState_(); | ||
}); | ||
|
||
this.updateRemoteState_(); | ||
} | ||
} | ||
|
||
/** @override */ | ||
release() { | ||
if (this.video.remote && this.callbackId_ != -1) { | ||
this.video.remote.cancelWatchAvailability(this.callbackId_); | ||
} | ||
|
||
super.release(); | ||
} | ||
|
||
/** | ||
* @private | ||
*/ | ||
async updateRemoteState_() { | ||
if (this.controls.getCastProxy().canCast() && | ||
this.controls.isCastAllowed()) { | ||
shaka.ui.Utils.setDisplay(this.remoteButton_, false); | ||
if (this.callbackId_ != -1) { | ||
this.video.remote.cancelWatchAvailability(this.callbackId_); | ||
this.callbackId_ = -1; | ||
} | ||
} else if (this.video.remote.state == 'disconnected') { | ||
const handleAvailabilityChange = (availability) => { | ||
if (this.player) { | ||
const loadMode = this.player.getLoadMode(); | ||
const srcMode = loadMode == shaka.Player.LoadMode.SRC_EQUALS; | ||
shaka.ui.Utils.setDisplay( | ||
this.remoteButton_, srcMode && availability); | ||
} else { | ||
shaka.ui.Utils.setDisplay(this.remoteButton_, false); | ||
} | ||
}; | ||
try { | ||
if (this.callbackId_ != -1) { | ||
await this.video.remote.cancelWatchAvailability(this.callbackId_); | ||
this.callbackId_ = -1; | ||
} | ||
} catch (e) { | ||
// Ignore this error. | ||
} | ||
try { | ||
const id = await this.video.remote.watchAvailability( | ||
handleAvailabilityChange); | ||
this.callbackId_ = id; | ||
} catch (e) { | ||
handleAvailabilityChange(/* availability= */ true); | ||
} | ||
} else if (this.callbackId_ != -1) { | ||
// If remote device is connecting or connected, we should stop | ||
// watching remote device availability to save power. | ||
await this.video.remote.cancelWatchAvailability(this.callbackId_); | ||
this.callbackId_ = -1; | ||
} | ||
} | ||
|
||
/** | ||
* @private | ||
*/ | ||
updateLocalizedStrings_() { | ||
const LocIds = shaka.ui.Locales.Ids; | ||
let text = this.localization.resolve(LocIds.CAST); | ||
const safariVersion = shaka.util.Platform.safariVersion(); | ||
if (safariVersion && safariVersion >= 13) { | ||
text = this.localization.resolve(LocIds.AIRPLAY); | ||
} | ||
this.remoteButton_.ariaLabel = text; | ||
this.remoteNameSpan_.textContent = text; | ||
} | ||
}; | ||
|
||
|
||
/** | ||
* @implements {shaka.extern.IUIElement.Factory} | ||
* @final | ||
*/ | ||
shaka.ui.RemoteButton.Factory = class { | ||
/** @override */ | ||
create(rootElement, controls) { | ||
return new shaka.ui.RemoteButton(rootElement, controls); | ||
} | ||
}; | ||
|
||
shaka.ui.OverflowMenu.registerElement( | ||
'remote', new shaka.ui.RemoteButton.Factory()); | ||
|
||
shaka.ui.Controls.registerElement( | ||
'remote', new shaka.ui.RemoteButton.Factory()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters