diff --git a/src/components/nowPlayingBar/nowPlayingBar.js b/src/components/nowPlayingBar/nowPlayingBar.js index 239b366a31..37400559f2 100644 --- a/src/components/nowPlayingBar/nowPlayingBar.js +++ b/src/components/nowPlayingBar/nowPlayingBar.js @@ -30,6 +30,7 @@ let volumeSlider; let volumeSliderContainer; let playPauseButtons; let positionSlider; +let toggleAirPlayButton; let toggleRepeatButton; let toggleRepeatButtonIcon; @@ -78,6 +79,8 @@ function getNowPlayingBarHtml() { html += ''; html += ''; + html += ''; + html += ''; html += ''; @@ -192,6 +195,13 @@ function bindEvents(elem) { } }); + toggleAirPlayButton = elem.querySelector('.btnAirPlay'); + toggleAirPlayButton.addEventListener('click', function () { + if (currentPlayer) { + playbackManager.toggleAirPlay(currentPlayer); + } + }); + elem.querySelector('.btnShuffleQueue').addEventListener('click', function () { if (currentPlayer) { playbackManager.toggleQueueShuffleMode(); @@ -328,6 +338,9 @@ function updatePlayerStateInternal(event, state, player) { toggleRepeatButton.classList.remove('hide'); } + const hideAirPlayButton = supportedCommands.indexOf('AirPlay') === -1; + toggleAirPlayButton.classList.toggle('hide', hideAirPlayButton); + updateRepeatModeDisplay(playbackManager.getRepeatMode()); onQueueShuffleModeChange(); diff --git a/src/plugins/htmlAudioPlayer/plugin.js b/src/plugins/htmlAudioPlayer/plugin.js index 2d0c6cceb1..2742e8d8cc 100644 --- a/src/plugins/htmlAudioPlayer/plugin.js +++ b/src/plugins/htmlAudioPlayer/plugin.js @@ -379,6 +379,10 @@ class HtmlAudioPlayer { return getDefaultProfile(); } + toggleAirPlay() { + return this.setAirPlayEnabled(!this.isAirPlayEnabled()); + } + // Save this for when playback stops, because querying the time at that point might return 0 currentTime(val) { const mediaElement = this._mediaElement; @@ -520,6 +524,33 @@ class HtmlAudioPlayer { return false; } + isAirPlayEnabled() { + if (document.AirPlayEnabled) { + return !!document.AirplayElement; + } + return false; + } + + setAirPlayEnabled(isEnabled) { + const mediaElement = this._mediaElement; + + if (mediaElement) { + if (document.AirPlayEnabled) { + if (isEnabled) { + mediaElement.requestAirPlay().catch(function(err) { + console.error('Error requesting AirPlay', err); + }); + } else { + document.exitAirPLay().catch(function(err) { + console.error('Error exiting AirPlay', err); + }); + } + } else { + mediaElement.webkitShowPlaybackTargetPicker(); + } + } + } + supports(feature) { if (!supportedFeatures) { supportedFeatures = getSupportedFeatures(); @@ -539,6 +570,10 @@ function getSupportedFeatures() { list.push('PlaybackRate'); } + if (browser.safari) { + list.push('AirPlay'); + } + return list; }