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(controls): Base React controls for Dash #1390

Merged
merged 3 commits into from
May 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
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
34 changes: 34 additions & 0 deletions src/lib/viewers/controls/media/_styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,37 @@ $bp-MediaControl-width: 36px;
@mixin bp-MediaButton {
@include bp-Control($width: $bp-MediaControl-width, $height: $bp-MediaControl-height);
}

@mixin bp-VideoControls {
display: flex;
flex-direction: column;
width: 100%;
background-image: linear-gradient(to top, rgba($black, .6) 0%, rgba($black, 0) 100%);

.bp-FullscreenToggle {
width: $bp-MediaControl-width;
height: $bp-MediaControl-height;
}
}

@mixin bp-VideoControls-bar {
display: flex;
justify-content: space-between;
padding: 0 10px 5px;
}

@mixin bp-VideoControls-group {
display: flex;
align-items: center;
}

@mixin bp-VideoControls-settings {
.bp-SettingsFlyout {
right: 15px;
}

.bp-SettingsToggle {
width: $bp-MediaControl-width;
height: $bp-MediaControl-height;
}
}
17 changes: 17 additions & 0 deletions src/lib/viewers/media/DashControls.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@import '../controls//media/styles';

.bp-DashControls {
@include bp-VideoControls;
}

.bp-DashControls-bar {
@include bp-VideoControls-bar;
}

.bp-DashControls-group {
@include bp-VideoControls-group;
}

.bp-DashControls-settings {
@include bp-VideoControls-settings;
}
63 changes: 63 additions & 0 deletions src/lib/viewers/media/DashControls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from 'react';
import DurationLabels, { Props as DurationLabelsProps } from '../controls/media/DurationLabels';
import MediaFullscreenToggle, { Props as MediaFullscreenToggleProps } from '../controls/media/MediaFullscreenToggle';
import MediaSettings, { Props as MediaSettingsProps } from '../controls/media/MediaSettings';
import PlayPauseToggle, { Props as PlayControlsProps } from '../controls/media/PlayPauseToggle';
import TimeControls, { Props as TimeControlsProps } from '../controls/media/TimeControls';
import VolumeControls, { Props as VolumeControlsProps } from '../controls/media/VolumeControls';
import './DashControls.scss';

export type Props = DurationLabelsProps &
MediaFullscreenToggleProps &
MediaSettingsProps &
PlayControlsProps &
TimeControlsProps &
VolumeControlsProps;

export default function DashControls({
autoplay,
bufferedRange,
currentTime,
durationTime,
isPlaying,
onAutoplayChange,
onFullscreenToggle,
onMuteChange,
onPlayPause,
onRateChange,
onTimeChange,
onVolumeChange,
rate,
volume,
}: Props): JSX.Element {
return (
<div className="bp-DashControls">
<TimeControls
bufferedRange={bufferedRange}
currentTime={currentTime}
durationTime={durationTime}
onTimeChange={onTimeChange}
/>

<div className="bp-DashControls-bar">
<div className="bp-DashControls-group">
<PlayPauseToggle isPlaying={isPlaying} onPlayPause={onPlayPause} />
<VolumeControls onMuteChange={onMuteChange} onVolumeChange={onVolumeChange} volume={volume} />
<DurationLabels currentTime={currentTime} durationTime={durationTime} />
</div>

<div className="bp-DashControls-group">
{/* CC Toggle */}
<MediaSettings
autoplay={autoplay}
className="bp-DashControls-settings"
onAutoplayChange={onAutoplayChange}
onRateChange={onRateChange}
rate={rate}
/>
<MediaFullscreenToggle onFullscreenToggle={onFullscreenToggle} />
</div>
</div>
</div>
);
}
58 changes: 49 additions & 9 deletions src/lib/viewers/media/DashViewer.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import VideoBaseViewer from './VideoBaseViewer';
import PreviewError from '../../PreviewError';
import React from 'react';
import DashControls from './DashControls';
import fullscreen from '../../Fullscreen';
import getLanguageName from '../../lang';
import PreviewError from '../../PreviewError';
import Timer from '../../Timer';
import VideoBaseViewer from './VideoBaseViewer';
import { appendQueryParams, getProp } from '../../util';
import { ERROR_CODE, VIEWER_EVENT, MEDIA_METRIC, MEDIA_METRIC_EVENTS } from '../../events';
import { getRepresentation } from '../../file';
import { MEDIA_STATIC_ASSETS_VERSION } from '../../constants';
import getLanguageName from '../../lang';
import { ERROR_CODE, VIEWER_EVENT, MEDIA_METRIC, MEDIA_METRIC_EVENTS } from '../../events';
import './Dash.scss';

const CSS_CLASS_DASH = 'bp-media-dash';
Expand Down Expand Up @@ -685,26 +687,36 @@ class DashViewer extends VideoBaseViewer {
}

this.calculateVideoDimensions();
this.loadUI();
if (this.getViewerOption('useReactControls')) {
this.loadUIReact();
} else {
this.loadUI();
}

if (this.isAutoplayEnabled()) {
this.autoplay();
}

this.loadFilmStrip();
if (!this.getViewerOption('useReactControls')) {
this.loadFilmStrip();
}
this.resize();
this.handleVolume();
this.startBandwidthTracking();
this.loadSubtitles();
this.loadAlternateAudio();
if (!this.getViewerOption('useReactControls')) {
this.loadSubtitles();
this.loadAlternateAudio();
}
this.showPlayButton();

this.loaded = true;
this.emit(VIEWER_EVENT.load);

// Make media element visible after resize
this.showMedia();
this.mediaControls.show();
if (!this.getViewerOption('useReactControls')) {
this.mediaControls.show();
}

if (this.options.autoFocus) {
this.mediaContainerEl.focus();
Expand Down Expand Up @@ -911,6 +923,34 @@ class DashViewer extends VideoBaseViewer {

return super.onKeydown(key);
}

/**
* @inheritdoc
*/
renderUI() {
if (!this.controls) {
return;
}

this.controls.render(
<DashControls
autoplay={this.isAutoplayEnabled()}
bufferedRange={this.mediaEl.buffered}
currentTime={this.mediaEl.currentTime}
durationTime={this.mediaEl.duration}
isPlaying={!this.mediaEl.paused}
onAutoplayChange={this.setAutoplay}
onFullscreenToggle={this.toggleFullscreen}
onMuteChange={this.toggleMute}
onPlayPause={this.togglePlay}
onRateChange={this.setRate}
onTimeChange={this.handleTimeupdateFromMediaControls}
onVolumeChange={this.setVolume}
rate={this.getRate()}
volume={this.mediaEl.volume}
/>,
);
}
}

export default DashViewer;
8 changes: 0 additions & 8 deletions src/lib/viewers/media/MP4.scss
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,4 @@
opacity: 1;
}
}

.bp-VideoControlsRoot {
position: absolute;
right: 0;
bottom: -1px;
left: 0;
width: 100%;
}
}
26 changes: 4 additions & 22 deletions src/lib/viewers/media/MP4Controls.scss
Original file line number Diff line number Diff line change
@@ -1,35 +1,17 @@
@import '../controls/media/styles';

.bp-MP4Controls {
display: flex;
flex-direction: column;
width: 100%;
background-image: linear-gradient(to top, rgba($black, .6) 0%, rgba($black, 0) 100%);

.bp-FullscreenToggle {
width: $bp-MediaControl-width;
height: $bp-MediaControl-height;
}
@include bp-VideoControls;
}

.bp-MP4Controls-bar {
display: flex;
justify-content: space-between;
padding: 0 10px 5px;
@include bp-VideoControls-bar;
}

.bp-MP4Controls-group {
display: flex;
align-items: center;
@include bp-VideoControls-group;
}

.bp-MP4Controls-settings {
.bp-SettingsFlyout {
right: 15px;
}

.bp-SettingsToggle {
width: $bp-MediaControl-width;
height: $bp-MediaControl-height;
}
@include bp-VideoControls-settings;
}
44 changes: 44 additions & 0 deletions src/lib/viewers/media/__tests__/DashControls-test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react';
import { shallow } from 'enzyme';
import DashControls from '../DashControls';
import MediaFullscreenToggle from '../../controls/media/MediaFullscreenToggle';
import MediaSettings from '../../controls/media/MediaSettings';
import PlayPauseToggle from '../../controls/media/PlayPauseToggle';
import TimeControls from '../../controls/media/TimeControls';
import VolumeControls from '../../controls/media/VolumeControls';

describe('DashControls', () => {
describe('render', () => {
test('should return a valid wrapper', () => {
const onAutoplayChange = jest.fn();
const onFullscreenToggle = jest.fn();
const onMuteChange = jest.fn();
const onRateChange = jest.fn();
const onPlayPause = jest.fn();
const onTimeChange = jest.fn();
const onVolumeChange = jest.fn();
const wrapper = shallow(
<DashControls
autoplay={false}
onAutoplayChange={onAutoplayChange}
onFullscreenToggle={onFullscreenToggle}
onMuteChange={onMuteChange}
onPlayPause={onPlayPause}
onRateChange={onRateChange}
onTimeChange={onTimeChange}
onVolumeChange={onVolumeChange}
rate="1.0"
/>,
);

expect(wrapper.hasClass('bp-DashControls')).toBe(true);
expect(wrapper.find(MediaFullscreenToggle).prop('onFullscreenToggle')).toEqual(onFullscreenToggle);
expect(wrapper.find(MediaSettings).prop('onAutoplayChange')).toEqual(onAutoplayChange);
expect(wrapper.find(MediaSettings).prop('onRateChange')).toEqual(onRateChange);
expect(wrapper.find(PlayPauseToggle).prop('onPlayPause')).toEqual(onPlayPause);
expect(wrapper.find(TimeControls).prop('onTimeChange')).toEqual(onTimeChange);
expect(wrapper.find(VolumeControls).prop('onMuteChange')).toEqual(onMuteChange);
expect(wrapper.find(VolumeControls).prop('onVolumeChange')).toEqual(onVolumeChange);
});
});
});
54 changes: 54 additions & 0 deletions src/lib/viewers/media/__tests__/DashViewer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,29 @@ describe('lib/viewers/media/DashViewer', () => {
expect(dash.mediaControls.show).toBeCalled();
expect(dash.loadUI).toBeCalled();
});

describe('With react controls', () => {
beforeEach(() => {
jest.spyOn(dash, 'calculateVideoDimensions').mockImplementation();
jest.spyOn(dash, 'getViewerOption').mockImplementation(() => true);
jest.spyOn(dash, 'loadAlternateAudio').mockImplementation();
jest.spyOn(dash, 'loadFilmStrip').mockImplementation();
jest.spyOn(dash, 'loadSubtitles').mockImplementation();
jest.spyOn(dash, 'loadUIReact').mockImplementation();
jest.spyOn(dash, 'loadUI').mockImplementation();
jest.spyOn(dash, 'resize').mockImplementation();
});

test('should call loadUIReact', () => {
dash.loadeddataHandler();

expect(dash.loadUIReact).toBeCalled();
expect(dash.loadUI).not.toBeCalled();
expect(dash.loadFilmStrip).not.toBeCalled();
expect(dash.loadSubtitles).not.toBeCalled();
expect(dash.loadAlternateAudio).not.toBeCalled();
});
});
});

describe('loadUI()', () => {
Expand Down Expand Up @@ -1442,4 +1465,35 @@ describe('lib/viewers/media/DashViewer', () => {
expect(dash.determineWatchLength()).toBe(10000);
});
});

describe('renderUI()', () => {
const getProps = instance => instance.controls.render.mock.calls[0][0].props;

beforeEach(() => {
jest.spyOn(dash, 'getViewerOption').mockImplementation(() => true);
dash.controls = {
destroy: jest.fn(),
render: jest.fn(),
};
});

test('should render react controls with the correct props', () => {
dash.renderUI();

expect(getProps(dash)).toMatchObject({
autoplay: false,
currentTime: expect.any(Number),
isPlaying: expect.any(Boolean),
onAutoplayChange: dash.setAutoplay,
onFullscreenToggle: dash.toggleFullscreen,
onMuteChange: dash.toggleMute,
onPlayPause: dash.togglePlay,
onRateChange: dash.setRate,
onTimeChange: dash.handleTimeupdateFromMediaControls,
onVolumeChange: dash.setVolume,
rate: '1.0',
volume: expect.any(Number),
});
});
});
});
8 changes: 8 additions & 0 deletions src/lib/viewers/media/_mediaBase.scss
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,11 @@
display: none;
}
}

.bp-VideoControlsRoot {
position: absolute;
right: 0;
bottom: -1px;
left: 0;
width: 100%;
}