-
Notifications
You must be signed in to change notification settings - Fork 88
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
Add Fast-Forward/Rewind button components #623
Changes from 12 commits
7ee6a97
8ac966f
2e4b3ae
58ede4e
5ce12aa
6f15842
118ed31
cb8da93
3be85a7
c755612
b39d0ed
345fcb2
0f5cb18
dfd9654
9dfd4aa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
@import '../variables'; | ||
@import '../mixins'; | ||
|
||
.#{$prefix}-ui-quickseekbutton { | ||
@extend %ui-button; | ||
|
||
&:hover { | ||
@include svg-icon-shadow; | ||
} | ||
|
||
&[data-#{$prefix}-seek-direction='forward'] { | ||
background-image: url('../../assets/skin-modern/images/quickseek-fastforward.svg'); | ||
} | ||
|
||
&[data-#{$prefix}-seek-direction='rewind'] { | ||
background-image: url('../../assets/skin-modern/images/quickseek-rewind.svg'); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,93 @@ | ||||||||||||||||||||||
import { Button, ButtonConfig } from './button'; | ||||||||||||||||||||||
import { i18n } from '../localization/i18n'; | ||||||||||||||||||||||
import { PlayerAPI } from 'bitmovin-player'; | ||||||||||||||||||||||
import { UIInstanceManager } from '../uimanager'; | ||||||||||||||||||||||
import { PlayerUtils } from '../playerutils'; | ||||||||||||||||||||||
|
||||||||||||||||||||||
export interface QuickSeekButtonConfig extends ButtonConfig { | ||||||||||||||||||||||
/** | ||||||||||||||||||||||
* Specify how many seconds the player should seek forward/backwards in the stream. | ||||||||||||||||||||||
* Negative values mean a backwards seek, positive values mean a forward seek. | ||||||||||||||||||||||
* Default is -10. | ||||||||||||||||||||||
*/ | ||||||||||||||||||||||
seekSeconds?: number; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
export class QuickSeekButton extends Button<QuickSeekButtonConfig> { | ||||||||||||||||||||||
constructor(config: QuickSeekButtonConfig = {}) { | ||||||||||||||||||||||
super(config); | ||||||||||||||||||||||
|
||||||||||||||||||||||
this.config = this.mergeConfig( | ||||||||||||||||||||||
config, | ||||||||||||||||||||||
{ | ||||||||||||||||||||||
seekSeconds: -10, | ||||||||||||||||||||||
cssClass: 'ui-quickseekbutton', | ||||||||||||||||||||||
}, | ||||||||||||||||||||||
this.config, | ||||||||||||||||||||||
); | ||||||||||||||||||||||
|
||||||||||||||||||||||
const seekDirection = this.config.seekSeconds < 0 ? 'rewind' : 'forward'; | ||||||||||||||||||||||
|
||||||||||||||||||||||
this.config.text = this.config.text || i18n.getLocalizer(`quickseek.${seekDirection}`); | ||||||||||||||||||||||
this.config.ariaLabel = this.config.ariaLabel || i18n.getLocalizer(`quickseek.${seekDirection}`); | ||||||||||||||||||||||
|
||||||||||||||||||||||
this.getDomElement().data(this.prefixCss('seek-direction'), seekDirection); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
configure(player: PlayerAPI, uimanager: UIInstanceManager): void { | ||||||||||||||||||||||
super.configure(player, uimanager); | ||||||||||||||||||||||
|
||||||||||||||||||||||
let isLive: boolean; | ||||||||||||||||||||||
let hasTimeShift: boolean; | ||||||||||||||||||||||
|
||||||||||||||||||||||
const switchVisibility = (isLive: boolean, hasTimeShift: boolean) => { | ||||||||||||||||||||||
if (isLive && !hasTimeShift) { | ||||||||||||||||||||||
this.hide(); | ||||||||||||||||||||||
} else { | ||||||||||||||||||||||
this.show(); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
}; | ||||||||||||||||||||||
|
||||||||||||||||||||||
const timeShiftDetector = new PlayerUtils.TimeShiftAvailabilityDetector(player); | ||||||||||||||||||||||
timeShiftDetector.onTimeShiftAvailabilityChanged.subscribe( | ||||||||||||||||||||||
(sender, args: PlayerUtils.TimeShiftAvailabilityChangedArgs) => { | ||||||||||||||||||||||
hasTimeShift = args.timeShiftAvailable; | ||||||||||||||||||||||
switchVisibility(isLive, hasTimeShift); | ||||||||||||||||||||||
}, | ||||||||||||||||||||||
); | ||||||||||||||||||||||
|
||||||||||||||||||||||
let liveStreamDetector = new PlayerUtils.LiveStreamDetector(player, uimanager); | ||||||||||||||||||||||
liveStreamDetector.onLiveChanged.subscribe((sender, args: PlayerUtils.LiveStreamDetectorEventArgs) => { | ||||||||||||||||||||||
isLive = args.live; | ||||||||||||||||||||||
switchVisibility(isLive, hasTimeShift); | ||||||||||||||||||||||
}); | ||||||||||||||||||||||
|
||||||||||||||||||||||
// Initial detection | ||||||||||||||||||||||
timeShiftDetector.detect(); | ||||||||||||||||||||||
liveStreamDetector.detect(); | ||||||||||||||||||||||
|
||||||||||||||||||||||
this.onClick.subscribe(() => { | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably out of scope, but it would be great if these would also trigger the seeks:
Maybe for a follow-up :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, I think this is out of scope.
I agree, but then this might interfere with the functionality we already have in bitmovin-player-ui/src/ts/components/seekbarcontroller.ts Lines 83 to 92 in 6e05cbd
I think this and/or a "HugeQuickSeekButton" for displaying within the video would be separate components (similar to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup, makes sense. Just wanted to note it down :D |
||||||||||||||||||||||
if (isLive && !hasTimeShift) { | ||||||||||||||||||||||
// If no DVR window is available, the button should be hidden anyway, so this is to be absolutely sure | ||||||||||||||||||||||
return; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
if (isLive && this.config.seekSeconds > 0 && player.getTimeShift() === 0) { | ||||||||||||||||||||||
// Don't do anything if the player is already on the live edge | ||||||||||||||||||||||
return; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
const currentPosition = isLive ? player.getTimeShift() : player.getCurrentTime(); | ||||||||||||||||||||||
|
||||||||||||||||||||||
const newSeekTime = currentPosition + this.config.seekSeconds; | ||||||||||||||||||||||
|
||||||||||||||||||||||
if (isLive) { | ||||||||||||||||||||||
const clampedValue = PlayerUtils.clampValueToRange(newSeekTime, player.getMaxTimeShift(), 0); | ||||||||||||||||||||||
player.timeShift(clampedValue); | ||||||||||||||||||||||
} else { | ||||||||||||||||||||||
const clampedValue = PlayerUtils.clampValueToRange(newSeekTime, 0, player.getDuration()); | ||||||||||||||||||||||
player.seek(clampedValue); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
}); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need any specific styling for the mobile UI variant?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO there is nothing special needed for now.