-
Notifications
You must be signed in to change notification settings - Fork 126
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #800 from tf/audio-api-volume-fading
Use Web Audio API for volume fading if available
- Loading branch information
Showing
11 changed files
with
239 additions
and
65 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
/** | ||
* Obtain the globally shared audio context. There can only be a | ||
* limited number of `AudioContext` objects in one page. | ||
* | ||
* @since edge | ||
*/ | ||
pageflow.audioContext = { | ||
/** | ||
* @returns [AudioContext] | ||
* Returns `null` if web audio API is not supported or creating | ||
* the context fails. | ||
*/ | ||
get: function() { | ||
var AudioContext = window.AudioContext || window.webkitAudioContext; | ||
|
||
if (typeof this._audioContext === 'undefined') { | ||
try { | ||
this._audioContext = AudioContext && new AudioContext(); | ||
} | ||
catch(e) { | ||
this._audioContext = null; | ||
pageflow.log('Failed to create AudioContext.', {force: true}); | ||
} | ||
} | ||
|
||
return this._audioContext; | ||
} | ||
}; |
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
5 changes: 5 additions & 0 deletions
5
app/assets/javascripts/pageflow/audio_player/get_media_element_method.js
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,5 @@ | ||
pageflow.AudioPlayer.getMediaElementMethod = function(player) { | ||
player.getMediaElement = function() { | ||
return player.audio.audio; | ||
}; | ||
}; |
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
79 changes: 14 additions & 65 deletions
79
app/assets/javascripts/pageflow/media_player/volume_fading.js
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 |
---|---|---|
@@ -1,68 +1,17 @@ | ||
pageflow.mediaPlayer.volumeFading = function(player) { | ||
var originalVolume = player.volume; | ||
var fadeVolumeDeferred; | ||
var fadeVolumeInterval; | ||
|
||
player.volume = function(value) { | ||
if (typeof value !== 'undefined') { | ||
cancelFadeVolume(); | ||
} | ||
|
||
return originalVolume.apply(player, arguments); | ||
}; | ||
|
||
player.fadeVolume = function(value, duration) { | ||
if (!pageflow.browser.has('volume control support')) { | ||
return new jQuery.Deferred().resolve().promise(); | ||
} | ||
|
||
cancelFadeVolume(); | ||
|
||
return new $.Deferred(function(deferred) { | ||
var resolution = 10; | ||
var startValue = volume(); | ||
var steps = duration / resolution; | ||
var leap = (value - startValue) / steps; | ||
|
||
if (value === startValue) { | ||
deferred.resolve(); | ||
} | ||
else { | ||
fadeVolumeDeferred = deferred; | ||
fadeVolumeInterval = setInterval(function() { | ||
volume(volume() + leap); | ||
|
||
if ((volume() >= value && value >= startValue) || | ||
(volume() <= value && value <= startValue)) { | ||
//= require_self | ||
//= require_tree ./volume_fading | ||
|
||
resolveFadeVolume(); | ||
} | ||
}, resolution); | ||
} | ||
}); | ||
|
||
function volume(/* arguments */) { | ||
return originalVolume.apply(player, arguments); | ||
} | ||
}; | ||
|
||
player.one('dispose', cancelFadeVolume); | ||
|
||
function resolveFadeVolume() { | ||
clearInterval(fadeVolumeInterval); | ||
fadeVolumeDeferred.resolve(); | ||
|
||
fadeVolumeInterval = null; | ||
fadeVolumeDeferred = null; | ||
pageflow.mediaPlayer.volumeFading = function(player) { | ||
if (!pageflow.browser.has('volume control support')) { | ||
return pageflow.mediaPlayer.volumeFading.noop(player); | ||
} | ||
|
||
function cancelFadeVolume() { | ||
if (fadeVolumeInterval) { | ||
clearInterval(fadeVolumeInterval); | ||
fadeVolumeDeferred.reject(); | ||
|
||
fadeVolumeInterval = null; | ||
fadeVolumeDeferred = null; | ||
} | ||
else if (pageflow.audioContext.get()) { | ||
return pageflow.mediaPlayer.volumeFading.webAudio( | ||
player, | ||
pageflow.audioContext.get() | ||
); | ||
} | ||
else { | ||
return pageflow.mediaPlayer.volumeFading.interval(player); | ||
} | ||
}; | ||
}; |
65 changes: 65 additions & 0 deletions
65
app/assets/javascripts/pageflow/media_player/volume_fading/interval.js
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,65 @@ | ||
pageflow.mediaPlayer.volumeFading.interval = function(player) { | ||
var originalVolume = player.volume; | ||
|
||
var fadeVolumeDeferred; | ||
var fadeVolumeInterval; | ||
|
||
player.volume = function(value) { | ||
if (typeof value !== 'undefined') { | ||
cancelFadeVolume(); | ||
} | ||
|
||
return originalVolume.apply(player, arguments); | ||
}; | ||
|
||
player.fadeVolume = function(value, duration) { | ||
cancelFadeVolume(); | ||
|
||
return new $.Deferred(function(deferred) { | ||
var resolution = 10; | ||
var startValue = volume(); | ||
var steps = duration / resolution; | ||
var leap = (value - startValue) / steps; | ||
|
||
if (value === startValue) { | ||
deferred.resolve(); | ||
} | ||
else { | ||
fadeVolumeDeferred = deferred; | ||
fadeVolumeInterval = setInterval(function() { | ||
volume(volume() + leap); | ||
|
||
if ((volume() >= value && value >= startValue) || | ||
(volume() <= value && value <= startValue)) { | ||
|
||
resolveFadeVolume(); | ||
} | ||
}, resolution); | ||
} | ||
}); | ||
}; | ||
|
||
player.one('dispose', cancelFadeVolume); | ||
|
||
function volume(/* arguments */) { | ||
return originalVolume.apply(player, arguments); | ||
} | ||
|
||
function resolveFadeVolume() { | ||
clearInterval(fadeVolumeInterval); | ||
fadeVolumeDeferred.resolve(); | ||
|
||
fadeVolumeInterval = null; | ||
fadeVolumeDeferred = null; | ||
} | ||
|
||
function cancelFadeVolume() { | ||
if (fadeVolumeInterval) { | ||
clearInterval(fadeVolumeInterval); | ||
fadeVolumeDeferred.reject(); | ||
|
||
fadeVolumeInterval = null; | ||
fadeVolumeDeferred = null; | ||
} | ||
} | ||
}; |
5 changes: 5 additions & 0 deletions
5
app/assets/javascripts/pageflow/media_player/volume_fading/noop.js
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,5 @@ | ||
pageflow.mediaPlayer.volumeFading.noop = function(player) { | ||
player.fadeVolume = function(value, duration) { | ||
return new jQuery.Deferred().resolve().promise(); | ||
}; | ||
}; |
109 changes: 109 additions & 0 deletions
109
app/assets/javascripts/pageflow/media_player/volume_fading/web_audio.js
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,109 @@ | ||
pageflow.mediaPlayer.volumeFading.webAudio = function(player, audioContext) { | ||
var gainNode; | ||
|
||
var currentDeferred; | ||
var currentTimeout; | ||
|
||
var currentValue = 1; | ||
|
||
var lastStartTime; | ||
var lastDuration; | ||
var lastStartValue; | ||
|
||
var allowedMinValue = 0.000001; | ||
|
||
player.volume = function(value) { | ||
ensureGainNode(); | ||
|
||
if (typeof value !== 'undefined') { | ||
cancel(); | ||
currentValue = ensureInAllowedRange(value); | ||
|
||
return gainNode.gain.setValueAtTime(currentValue, | ||
audioContext.currentTime); | ||
} | ||
|
||
return Math.round(currentValue * 100) / 100; | ||
}; | ||
|
||
player.fadeVolume = function(value, duration) { | ||
ensureGainNode(); | ||
|
||
cancel(); | ||
recordFadeStart(duration); | ||
|
||
currentValue = ensureInAllowedRange(value); | ||
|
||
gainNode.gain.setValueAtTime(lastStartValue, audioContext.currentTime); | ||
gainNode.gain.linearRampToValueAtTime(currentValue, | ||
audioContext.currentTime + duration / 1000); | ||
|
||
return new $.Deferred(function(deferred) { | ||
currentTimeout = setTimeout(resolve, duration); | ||
currentDeferred = deferred; | ||
}).promise(); | ||
}; | ||
|
||
player.one('dispose', cancel); | ||
|
||
function ensureGainNode() { | ||
if (!gainNode) { | ||
gainNode = audioContext.createGain(); | ||
|
||
var source = audioContext.createMediaElementSource(player.getMediaElement()); | ||
|
||
source.connect(gainNode); | ||
gainNode.connect(audioContext.destination); | ||
} | ||
} | ||
|
||
function resolve() { | ||
clearTimeout(currentTimeout); | ||
currentDeferred.resolve(); | ||
|
||
currentTimeout = null; | ||
currentDeferred = null; | ||
} | ||
|
||
function cancel() { | ||
if (currentDeferred) { | ||
gainNode.gain.cancelScheduledValues(audioContext.currentTime); | ||
|
||
clearTimeout(currentTimeout); | ||
currentDeferred.reject(); | ||
|
||
currentTimeout = null; | ||
currentDeferred = null; | ||
|
||
updateCurrentValueFromComputedValue(); | ||
} | ||
} | ||
|
||
function recordFadeStart(duration) { | ||
lastStartTime = audioContext.currentTime; | ||
lastStartValue = currentValue; | ||
lastDuration = duration; | ||
} | ||
|
||
function updateCurrentValueFromComputedValue() { | ||
// Firefox 54 on Ubuntu does not provide computed values when gain | ||
// was changed via one of the scheduling methods. Instead | ||
// gain.value always reports 1. Interpolate manually do determine | ||
// how far the fade was performed before cancel was called. | ||
if (gainNode.gain.value == 1) { | ||
var performedDuration = (audioContext.currentTime - lastStartTime) * 1000; | ||
var lastDelta = currentValue - lastStartValue; | ||
|
||
currentValue = ensureInAllowedRange( | ||
lastStartValue + (performedDuration / lastDuration * lastDelta) | ||
); | ||
} | ||
else { | ||
currentValue = gainNode.gain.value; | ||
} | ||
} | ||
|
||
function ensureInAllowedRange(value) { | ||
return value < allowedMinValue ? allowedMinValue : value; | ||
} | ||
}; |
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
5 changes: 5 additions & 0 deletions
5
app/assets/javascripts/pageflow/video_player/get_media_element_method.js
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,5 @@ | ||
pageflow.VideoPlayer.getMediaElementMethod = function(player) { | ||
player.getMediaElement = function() { | ||
return player.tech({IWillNotUseThisInPlugins: true}).el(); | ||
}; | ||
}; |
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