Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(UI): Add double tap to forward/rewind in the video #5943

Merged
merged 2 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build/types/ui
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
+../../ui/element.js
+../../ui/fast_forward_button.js
+../../ui/fullscreen_button.js
+../../ui/hidden_fast_forward_button.js
+../../ui/hidden_rewind_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
119 changes: 119 additions & 0 deletions ui/hidden_fast_forward_button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

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

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

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

/**
* @extends {shaka.ui.Element}
* @final
* @export
*/
shaka.ui.HiddenFastForwardButton = 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 fast forward button on video Container.
* When the timer ticks it will force button to be invisible.
*
* @private {shaka.util.Timer}
*/
this.hideFastForwardButtonContainerTimer_ = new shaka.util.Timer(() => {
this.hideFastForwardButtonContainer_();
});


/** @private {!HTMLElement} */
this.fastforwardContainer_ = shaka.util.Dom.createHTMLElement('div');
this.fastforwardContainer_.classList.add(
'shaka-fast-foward-container');
this.parent.appendChild(this.fastforwardContainer_);

this.eventManager.listen(
this.fastforwardContainer_, '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.onFastForwardButtonClick_();
}
});

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

/** @private {!HTMLElement} */
this.fastforwardIcon_ = shaka.util.Dom.createHTMLElement('span');
this.fastforwardIcon_.classList.add(
'shaka-forward-rewind-container-icon');
this.fastforwardIcon_.textContent =
shaka.ui.Enums.MaterialDesignIcons.FAST_FORWARD;
this.fastforwardContainer_.appendChild(this.fastforwardIcon_);
}

/**
* @private
*/
onFastForwardButtonClick_() {
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.hideFastForwardButtonContainerTimer_.tickAfter(1);
} else if (this.lastTouchEventTimeSet_+1000 > Date.now()) {
// stops hidding of fast-forward button incase the timmer is active
// because of previous touch event.
this.hideFastForwardButtonContainerTimer_.stop();
this.lastTouchEventTimeSet_ = Date.now();
const position =
parseInt(this.fastForwardValue_.textContent, 10) + tapSeekDistance;
this.fastForwardValue_.textContent = position.toString() + 's';
this.fastforwardContainer_.style.opacity = '1';
this.hideFastForwardButtonContainerTimer_.tickAfter(1);
}
}

/**
* @private
*/
hideFastForwardButtonContainer_() {
// Prevent adding seek value if its a single tap.
if (parseInt(this.fastForwardValue_.textContent, 10) != 0) {
this.video.currentTime = this.controls.getDisplayTime() + parseInt(
this.fastForwardValue_.textContent, 10);
}
this.fastforwardContainer_.style.opacity = '0';
this.triggeredTouchValid_ = false;
this.fastForwardValue_.textContent = '0s';
}
};
117 changes: 117 additions & 0 deletions ui/hidden_rewind_button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

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

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

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

/**
* @extends {shaka.ui.Element}
* @final
* @export
*/
shaka.ui.HiddenRewindButton = class extends shaka.ui.Element {
theodab marked this conversation as resolved.
Show resolved Hide resolved
/**
* @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 rewind button on video Container.
* When the timer ticks it will force button to be invisible.
*
* @private {shaka.util.Timer}
*/
this.hideRewindButtonContainerTimer_ = new shaka.util.Timer(() => {
this.hideRewindButtonContainer_();
});

/** @private {!HTMLElement} */
this.rewindContainer_ = shaka.util.Dom.createHTMLElement('div');
this.rewindContainer_.classList.add(
'shaka-rewind-container');
this.parent.appendChild(this.rewindContainer_);

this.eventManager.listen(this.rewindContainer_, '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.onRewindButtonClick_();
}
});

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

/** @private {!HTMLElement} */
this.rewindIcon_ = shaka.util.Dom.createHTMLElement('span');
this.rewindIcon_.classList.add(
'shaka-forward-rewind-container-icon');
this.rewindIcon_.textContent =
shaka.ui.Enums.MaterialDesignIcons.REWIND;
this.rewindContainer_.appendChild(this.rewindIcon_);
}

/**
* @private
*/
onRewindButtonClick_() {
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.hideRewindButtonContainerTimer_.tickAfter(1);
} else if (this.lastTouchEventTimeSet_+1000 > Date.now()) {
// stops hidding of fast-forward button incase the timmer is active
// because of previous touch event.
this.hideRewindButtonContainerTimer_.stop();
this.lastTouchEventTimeSet_ = Date.now();
const position =
parseInt(this.rewindValue_.textContent, 10) - tapSeekDistance;
this.rewindValue_.textContent = position.toString() + 's';
this.rewindContainer_.style.opacity = '1';
this.hideRewindButtonContainerTimer_.tickAfter(1);
}
}

/**
* @private
*/
hideRewindButtonContainer_() {
// Prevent adding seek value if its a single tap.
if (parseInt(this.rewindValue_.textContent, 10) != 0) {
this.video.currentTime = this.controls.getDisplayTime() + parseInt(
this.rewindValue_.textContent, 10);
}
this.rewindContainer_.style.opacity = '0';
this.triggeredTouchValid_ = false;
this.rewindValue_.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;
}
Loading
Loading