Skip to content

Commit

Permalink
feat(UI): Add double tap to forward/rewind in the video (#5943)
Browse files Browse the repository at this point in the history
Closes #3357
Closes #3303

Thanks to @surajkumar-sk, his
#3373 has been the
inspiration for this.
  • Loading branch information
avelad authored Nov 29, 2023
1 parent 8b6602e commit 918c30b
Show file tree
Hide file tree
Showing 9 changed files with 313 additions and 1 deletion.
3 changes: 3 additions & 0 deletions build/types/ui
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
+../../ui/element.js
+../../ui/fast_forward_button.js
+../../ui/fullscreen_button.js
+../../ui/hidden_fast_forward_button.js
+../../ui/hidden_rewind_button.js
+../../ui/hidden_seek_button.js
+../../ui/language_utils.js
+../../ui/localization.js
+../../ui/loop_button.js
Expand Down
43 changes: 43 additions & 0 deletions ui/controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ goog.require('shaka.ui.AdCounter');
goog.require('shaka.ui.AdPosition');
goog.require('shaka.ui.BigPlayButton');
goog.require('shaka.ui.ContextMenu');
goog.require('shaka.ui.HiddenFastForwardButton');
goog.require('shaka.ui.HiddenRewindButton');
goog.require('shaka.ui.Locales');
goog.require('shaka.ui.Localization');
goog.require('shaka.ui.SeekBar');
Expand Down Expand Up @@ -847,6 +849,11 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
this.addBufferingSpinner_();
}

if (this.config_.seekOnTaps) {
this.addFastForwardButtonOnControlsContainer_();
this.addRewindButtonOnControlsContainer_();
}

this.addDaiAdContainer_();

this.addControlsButtonPanel_();
Expand Down Expand Up @@ -970,6 +977,42 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
svg.appendChild(spinnerCircle);
}

/**
* Add fast-forward button on Controls container for moving video some
* seconds ahead when the video is tapped more than once, video seeks ahead
* some seconds for every extra tap.
* @private
*/
addFastForwardButtonOnControlsContainer_() {
const hiddenFastForwardContainer = shaka.util.Dom.createHTMLElement('div');
hiddenFastForwardContainer.classList.add(
'shaka-hidden-fast-forward-container');
this.controlsContainer_.appendChild(hiddenFastForwardContainer);

/** @private {shaka.ui.HiddenFastForwardButton} */
this.hiddenFastForwardButton_ =
new shaka.ui.HiddenFastForwardButton(hiddenFastForwardContainer, this);
this.elements_.push(this.hiddenFastForwardButton_);
}

/**
* Add Rewind button on Controls container for moving video some seconds
* behind when the video is tapped more than once, video seeks behind some
* seconds for every extra tap.
* @private
*/
addRewindButtonOnControlsContainer_() {
const hiddenRewindContainer = shaka.util.Dom.createHTMLElement('div');
hiddenRewindContainer.classList.add(
'shaka-hidden-rewind-container');
this.controlsContainer_.appendChild(hiddenRewindContainer);

/** @private {shaka.ui.HiddenRewindButton} */
this.hiddenRewindButton_ =
new shaka.ui.HiddenRewindButton(hiddenRewindContainer, this);
this.elements_.push(this.hiddenRewindButton_);
}

/** @private */
addControlsButtonPanel_() {
/** @private {!HTMLElement} */
Expand Down
13 changes: 12 additions & 1 deletion ui/externs/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ shaka.extern.UIVolumeBarColors;
* keyboardLargeSeekDistance: number,
* fullScreenElement: HTMLElement,
* preferDocumentPictureInPicture: boolean,
* showAudioChannelCountVariants: boolean
* showAudioChannelCountVariants: boolean,
* seekOnTaps: boolean,
* tapSeekDistance: number
* }}
*
* @property {!Array.<string>} controlPanelElements
Expand Down Expand Up @@ -215,6 +217,15 @@ shaka.extern.UIVolumeBarColors;
* displayed or if, on the contrary, only the language should be displayed
* regardless of the channel count.
* Defaults to true.
* @property {boolean} seekOnTaps
* Indicates whether or not a fast-forward and rewind tap button that seeks
* video some seconds.
* Defaults to true.
* @property {number} tapSeekDistance
* The time interval, in seconds, to seek when the user presses the left or
* right part of the video. If less than or equal to 0,
* no seeking will occur.
* Defaults to 10 seconds.
* @exportDoc
*/
shaka.extern.UIConfiguration;
Expand Down
32 changes: 32 additions & 0 deletions ui/hidden_fast_forward_button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

goog.provide('shaka.ui.HiddenFastForwardButton');

goog.require('shaka.ui.Enums');
goog.require('shaka.ui.HiddenSeekButton');

goog.requireType('shaka.ui.Controls');

/**
* @extends {shaka.ui.HiddenSeekButton}
* @final
* @export
*/
shaka.ui.HiddenFastForwardButton = class extends shaka.ui.HiddenSeekButton {
/**
* @param {!HTMLElement} parent
* @param {!shaka.ui.Controls} controls
*/
constructor(parent, controls) {
super(parent, controls);

this.seekContainer.classList.add('shaka-fast-foward-container');
this.seekIcon.textContent =
shaka.ui.Enums.MaterialDesignIcons.FAST_FORWARD;
this.isRewind = false;
}
};
32 changes: 32 additions & 0 deletions ui/hidden_rewind_button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

goog.provide('shaka.ui.HiddenRewindButton');

goog.require('shaka.ui.Enums');
goog.require('shaka.ui.HiddenSeekButton');

goog.requireType('shaka.ui.Controls');

/**
* @extends {shaka.ui.HiddenSeekButton}
* @final
* @export
*/
shaka.ui.HiddenRewindButton = class extends shaka.ui.HiddenSeekButton {
/**
* @param {!HTMLElement} parent
* @param {!shaka.ui.Controls} controls
*/
constructor(parent, controls) {
super(parent, controls);

this.seekContainer.classList.add('shaka-rewind-container');
this.seekIcon.textContent =
shaka.ui.Enums.MaterialDesignIcons.REWIND;
this.isRewind = true;
}
};
121 changes: 121 additions & 0 deletions ui/hidden_seek_button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

goog.provide('shaka.ui.HiddenSeekButton');

goog.require('shaka.ui.Element');
goog.require('shaka.util.Timer');
goog.require('shaka.util.Dom');

goog.requireType('shaka.ui.Controls');

/**
* @extends {shaka.ui.Element}
* @export
*/
shaka.ui.HiddenSeekButton = class extends shaka.ui.Element {
/**
* @param {!HTMLElement} parent
* @param {!shaka.ui.Controls} controls
*/
constructor(parent, controls) {
super(parent, controls);

/** @private {?number} */
this.lastTouchEventTimeSet_ = null;

/** @private {?boolean} */
this.triggeredTouchValid_ = false;

/**
* This timer will be used to hide seek button on video Container.
* When the timer ticks it will force button to be invisible.
*
* @private {shaka.util.Timer}
*/
this.hideSeekButtonContainerTimer_ = new shaka.util.Timer(() => {
this.hideSeekButtonContainer_();
});

/** @protected {!HTMLElement} */
this.seekContainer = shaka.util.Dom.createHTMLElement('div');
this.parent.appendChild(this.seekContainer);

this.eventManager.listen(this.seekContainer, 'touchstart', (event) => {
// In case any settings menu are open this assigns the first touch
// to close the menu.
if (this.controls.anySettingsMenusAreOpen()) {
// prevent the default changes that browser triggers
event.preventDefault();
this.controls.hideSettingsMenus();
} else if (this.controls.getConfig().tapSeekDistance > 0) {
// prevent the default changes that browser triggers
event.preventDefault();
this.onSeekButtonClick_();
}
});

/** @private {!HTMLElement} */
this.seekValue_ = shaka.util.Dom.createHTMLElement('span');
this.seekValue_.textContent = '0s';
this.seekContainer.appendChild(this.seekValue_);


/** @protected {!HTMLElement} */
this.seekIcon = shaka.util.Dom.createHTMLElement('span');
this.seekIcon.classList.add(
'shaka-forward-rewind-container-icon');
this.seekContainer.appendChild(this.seekIcon);

/** @protected {boolean} */
this.isRewind = false;
}

/**
* @private
*/
onSeekButtonClick_() {
const tapSeekDistance = this.controls.getConfig().tapSeekDistance;
// This stores the time for first touch and makes touch valid for
// next 1s so incase the touch event is triggered again within 1s
// this if condition fails and the video seeking happens.
if (!this.triggeredTouchValid_) {
this.triggeredTouchValid_ = true;
this.lastTouchEventTimeSet_ = Date.now();
this.hideSeekButtonContainerTimer_.tickAfter(1);
} else if (this.lastTouchEventTimeSet_+1000 > Date.now()) {
// stops hidding of seek button incase the timmer is active
// because of previous touch event.
this.hideSeekButtonContainerTimer_.stop();
this.lastTouchEventTimeSet_ = Date.now();
let position = 0;
if (this.isRewind) {
position =
parseInt(this.seekValue_.textContent, 10) - tapSeekDistance;
} else {
position =
parseInt(this.seekValue_.textContent, 10) + tapSeekDistance;
}
this.seekValue_.textContent = position.toString() + 's';
this.seekContainer.style.opacity = '1';
this.hideSeekButtonContainerTimer_.tickAfter(1);
}
}

/**
* @private
*/
hideSeekButtonContainer_() {
// Prevent adding seek value if its a single tap.
if (parseInt(this.seekValue_.textContent, 10) != 0) {
this.video.currentTime = this.controls.getDisplayTime() + parseInt(
this.seekValue_.textContent, 10);
}
this.seekContainer.style.opacity = '0';
this.triggeredTouchValid_ = false;
this.seekValue_.textContent = '0s';
}
};
50 changes: 50 additions & 0 deletions ui/less/buttons.less
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,53 @@
outline: none;
}
}

.shaka-fast-foward-container,
.shaka-rewind-container {
height: 100%;
width: 100%;
.shrinkable();
.absolute-position();

/* Keep all the elements inside button div in center and in row */
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;

/* To be properly positioned, this should have no margin. */
margin: 0;

/* No border. */
border: none;

/* Setting text color to white */
color: rgb(255 255 255);

/* Setting background color to black so white text can be seen clearly */
background-color: rgb(0 0 0 / 50%);

cursor: default;
font-size: 20px;

/* Hidding the container by setting opacity */
opacity: 0;

/* Make the text inside this button unselectable */
.unselectable();
}

.shaka-fast-foward-container {
border-radius: 40% 0 0 40%;
}

.shaka-rewind-container {
border-radius: 0 40% 40% 0;
}

/* This class is instead of material-icon-round
* because the font-size of 24 doesn't look good */
.shaka-forward-rewind-container-icon {
font-family: "Material Icons Round";
font-size: 34px;
}
18 changes: 18 additions & 0 deletions ui/less/containers.less
Original file line number Diff line number Diff line change
Expand Up @@ -365,3 +365,21 @@
on a black background. */
filter: drop-shadow(0 0 2px rgba(255 255 255 / 50%));
}

.shaka-hidden-fast-forward-container,
.shaka-hidden-rewind-container {
height: 100%;
width: 40%;
.shrinkable();
}

.shaka-hidden-fast-forward-container {
/* Keep the fast forward button to the right of this container. */
.absolute-position();
left: 60%;
}

.shaka-hidden-rewind-container {
/* keep the rewind button to the left */
.absolute-position();
}
2 changes: 2 additions & 0 deletions ui/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,8 @@ shaka.ui.Overlay = class {
fullScreenElement: this.videoContainer_,
preferDocumentPictureInPicture: true,
showAudioChannelCountVariants: true,
seekOnTaps: true,
tapSeekDistance: 10,
};

// eslint-disable-next-line no-restricted-syntax
Expand Down

0 comments on commit 918c30b

Please sign in to comment.