Bug 1474149 - Allow media seek amount so be configured
When you play audio or video in Firefox, you can jump by a fixed amount of 15 seconds, as this help page explains: https://support.mozilla.org/en-US/kb/keyboard-shortcuts-perform-firefox-tasks-quickly#w_media-shortcuts.
Here are media files for testing. You may have to click on the pause/play button, before the keystrokes will be recognized:
- Audio: http://wilwheaton.typepad.com/files/wil_wheaton_vs_text_2_speech.mp3
- Video: http://www.belleslettres.eu/video/konjunktiv-irrealis.mp4
15 seconds is an unusual big seek step. These apps all have 5 seconds as their default seek step:
- YouTube
- foobar2000 (popular audio player for Windows)
- MPC-BE (video player for Windows)
So, a user should be able to modify the seek step length.
To accomplish that, this patch introduces the config option `media.seekStepLength`.
I changed `HTMLMediaElement`, because, in `videocontrols.xml`, unlike in other XBL files,
- `Services` (for `Services.prefs.getIntPref()`),
- `Cc` (for `Cc["@mozilla.org/preferences-service;1"]`) or
- `Components`/`Components.classes` (for `Components.classes["@mozilla.org/preferences-service;1"]`)
...were not available.
In accordance with the existing `HTMLMediaElement` API, the newly introduced property `mozSeekStepLength` has seconds as its unit. The `media.seekStepLength` config option is in milliseconds, because floats are converted from strings and milliseconds are already a popular unit in Firefox's preferences.
Should I add `, 15000` like in the following code, in case the config option was deleted?
```c++
double
HTMLMediaElement::MozSeekStepLength() const
{
return Preferences::GetInt("media.seekStepLength", 15000) /*ms*/ / 1000.0;
}
```
MozReview-Commit-ID: 97wsTqRC26M
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -3092,16 +3092,22 @@ HTMLMediaElement::SetMuted(bool aMuted)
DispatchAsyncEvent(NS_LITERAL_STRING("volumechange"));
// We allow inaudible autoplay. But changing our mute status may make this
// media audible. So pause if we are no longer supposed to be autoplaying.
PauseIfShouldNotBePlaying();
}
+double
+HTMLMediaElement::MozSeekStepLength() const
+{
+ return Preferences::GetInt("media.seekStepLength") /*ms*/ / 1000.0;
+}
+
class HTMLMediaElement::StreamCaptureTrackSource
: public MediaStreamTrackSource
, public MediaStreamTrackSource::Sink
{
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(StreamCaptureTrackSource,
MediaStreamTrackSource)
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -637,16 +637,18 @@ public:
return mIsCasting;
}
void SetMozIsCasting(bool aShow)
{
mIsCasting = aShow;
}
+ double MozSeekStepLength() const;
+
already_AddRefed<MediaSource> GetMozMediaSourceObject() const;
// Returns a string describing the state of the media player internal
// data. Used for debugging purposes.
void GetMozDebugReaderData(nsAString& aString);
// Returns a promise which will be resolved after collecting debugging
// data from decoder/reader/MDSM. Used for debugging purposes.
already_AddRefed<Promise> MozRequestDebugInfo(ErrorResult& aRv);
--- a/dom/webidl/HTMLMediaElement.webidl
+++ b/dom/webidl/HTMLMediaElement.webidl
@@ -115,18 +115,21 @@ partial interface HTMLMediaElement {
[Pref="media.test.dumpDebugInfo"]
Promise<void> mozDumpDebugInfo();
attribute MediaStream? srcObject;
attribute boolean mozPreservesPitch;
// NB: for internal use with the video controls:
- [Func="IsChromeOrXBL"] attribute boolean mozAllowCasting;
- [Func="IsChromeOrXBL"] attribute boolean mozIsCasting;
+ [Func="IsChromeOrXBL"]
+ attribute boolean mozAllowCasting;
+ [Func="IsChromeOrXBL"]
+ attribute boolean mozIsCasting;
+ readonly attribute double mozSeekStepLength;
// Mozilla extension: stream capture
[Throws]
MediaStream mozCaptureStream();
[Throws]
MediaStream mozCaptureStreamUntilEnded();
readonly attribute boolean mozAudioCaptured;
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5718,16 +5718,20 @@ pref("layout.css.color-adjust.enabled",
pref("dom.audiochannel.audioCompeting", false);
pref("dom.audiochannel.audioCompeting.allAgents", false);
// Default media volume
pref("media.default_volume", "1.0");
pref("media.seekToNextFrame.enabled", true);
+// The amount of milliseconds to seek backward or forward when using a fixed
+// (non-relative) seek step.
+pref("media.seekStepLength", 15000);
+
// return the maximum number of cores that navigator.hardwareCurrency returns
pref("dom.maxHardwareConcurrency", 16);
// Shutdown the osfile worker if its no longer needed.
#if !defined(RELEASE_OR_BETA)
pref("osfile.reset_worker_delay", 30000);
#endif
--- a/toolkit/content/widgets/videocontrols.xml
+++ b/toolkit/content/widgets/videocontrols.xml
@@ -1433,17 +1433,17 @@
},
keyHandler(event) {
// Ignore keys when content might be providing its own.
if (!this.video.hasAttribute("controls")) {
return;
}
- var keystroke = "";
+ let keystroke = "";
if (event.altKey) {
keystroke += "alt-";
}
if (event.shiftKey) {
keystroke += "shift-";
}
if (navigator.platform.startsWith("Mac")) {
if (event.metaKey) {
@@ -1481,17 +1481,17 @@
break;
}
if (String.fromCharCode(event.charCode) == " ") {
keystroke += "space";
}
this.log("Got keystroke: " + keystroke);
- var oldval, newval;
+ let oldval, newval;
try {
switch (keystroke) {
case "space": /* Play */
let target = event.originalTarget;
if (target.localName === "button" && !target.disabled) {
break;
}
@@ -1509,43 +1509,37 @@
this.video.muted = false;
break;
case "accel-downArrow": /* Mute */
this.video.muted = true;
break;
case "accel-upArrow": /* Unmute */
this.video.muted = false;
break;
- case "leftArrow": /* Seek back 15 seconds */
- case "accel-leftArrow": /* Seek back 10% */
- oldval = this.video.currentTime;
- if (keystroke == "leftArrow") {
- newval = oldval - 15;
- } else {
- newval = oldval - (this.video.duration || this.maxCurrentTimeSeen / 1000) / 10;
- }
- this.video.currentTime = (newval >= 0 ? newval : 0);
+ case "leftArrow": /* Seek backward... */
+ case "rightArrow": /* ...or forward a fixed amount */
+ case "accel-leftArrow": /* Seek backward... */
+ case "accel-rightArrow": { /* ...or forward 10% */
+ let maxTime = this.video.duration /*s*/ ||
+ this.maxCurrentTimeSeen /*ms*/ / 1000;
+ let seekStepLength = keystroke.startsWith("accel-") ?
+ maxTime * 0.10
+ : this.video.mozSeekStepLength /*s*/;
+ this.video.currentTime = keystroke.endsWith("leftArrow") ?
+ Math.max(this.video.currentTime - seekStepLength, 0) // <-
+ : Math.min(this.video.currentTime + seekStepLength, maxTime) // ->
break;
- case "rightArrow": /* Seek forward 15 seconds */
- case "accel-rightArrow": /* Seek forward 10% */
- oldval = this.video.currentTime;
- var maxtime = (this.video.duration || this.maxCurrentTimeSeen / 1000);
- if (keystroke == "rightArrow") {
- newval = oldval + 15;
- } else {
- newval = oldval + maxtime / 10;
- }
- this.video.currentTime = (newval <= maxtime ? newval : maxtime);
- break;
+ }
case "home": /* Seek to beginning */
this.video.currentTime = 0;
break;
case "end": /* Seek to end */
- if (this.video.currentTime != this.video.duration) {
- this.video.currentTime = (this.video.duration || this.maxCurrentTimeSeen / 1000);
+ if (this.video.currentTime /*s*/ != this.video.duration /*s*/) {
+ this.video.currentTime = this.video.duration /*s*/ ||
+ this.maxCurrentTimeSeen /*ms*/ / 1000;
}
break;
default:
return;
}
} catch (e) { /* ignore any exception from setting .currentTime */ }
event.preventDefault(); // Prevent page scrolling