diff --git a/src/lib/viewers/controls/media/_styles.scss b/src/lib/viewers/controls/media/_styles.scss index baa844e16..0325cc3c8 100644 --- a/src/lib/viewers/controls/media/_styles.scss +++ b/src/lib/viewers/controls/media/_styles.scss @@ -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; + } +} diff --git a/src/lib/viewers/media/DashControls.scss b/src/lib/viewers/media/DashControls.scss new file mode 100644 index 000000000..a943611f6 --- /dev/null +++ b/src/lib/viewers/media/DashControls.scss @@ -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; +} diff --git a/src/lib/viewers/media/DashControls.tsx b/src/lib/viewers/media/DashControls.tsx new file mode 100644 index 000000000..e7af8c605 --- /dev/null +++ b/src/lib/viewers/media/DashControls.tsx @@ -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 ( +
+ + +
+
+ + + +
+ +
+ {/* CC Toggle */} + + +
+
+
+ ); +} diff --git a/src/lib/viewers/media/DashViewer.js b/src/lib/viewers/media/DashViewer.js index 43ce1b196..61bab9a30 100644 --- a/src/lib/viewers/media/DashViewer.js +++ b/src/lib/viewers/media/DashViewer.js @@ -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'; @@ -685,18 +687,26 @@ 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; @@ -704,7 +714,9 @@ class DashViewer extends VideoBaseViewer { // 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(); @@ -911,6 +923,34 @@ class DashViewer extends VideoBaseViewer { return super.onKeydown(key); } + + /** + * @inheritdoc + */ + renderUI() { + if (!this.controls) { + return; + } + + this.controls.render( + , + ); + } } export default DashViewer; diff --git a/src/lib/viewers/media/MP4.scss b/src/lib/viewers/media/MP4.scss index e5273e9a4..fe397e3b8 100644 --- a/src/lib/viewers/media/MP4.scss +++ b/src/lib/viewers/media/MP4.scss @@ -34,12 +34,4 @@ opacity: 1; } } - - .bp-VideoControlsRoot { - position: absolute; - right: 0; - bottom: -1px; - left: 0; - width: 100%; - } } diff --git a/src/lib/viewers/media/MP4Controls.scss b/src/lib/viewers/media/MP4Controls.scss index e95344854..69c94d624 100644 --- a/src/lib/viewers/media/MP4Controls.scss +++ b/src/lib/viewers/media/MP4Controls.scss @@ -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; } diff --git a/src/lib/viewers/media/__tests__/DashControls-test.tsx b/src/lib/viewers/media/__tests__/DashControls-test.tsx new file mode 100644 index 000000000..a02134b95 --- /dev/null +++ b/src/lib/viewers/media/__tests__/DashControls-test.tsx @@ -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( + , + ); + + 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); + }); + }); +}); diff --git a/src/lib/viewers/media/__tests__/DashViewer-test.js b/src/lib/viewers/media/__tests__/DashViewer-test.js index e2c3bba67..e45aec57f 100644 --- a/src/lib/viewers/media/__tests__/DashViewer-test.js +++ b/src/lib/viewers/media/__tests__/DashViewer-test.js @@ -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()', () => { @@ -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), + }); + }); + }); }); diff --git a/src/lib/viewers/media/_mediaBase.scss b/src/lib/viewers/media/_mediaBase.scss index b96725750..31be33973 100644 --- a/src/lib/viewers/media/_mediaBase.scss +++ b/src/lib/viewers/media/_mediaBase.scss @@ -91,3 +91,11 @@ display: none; } } + +.bp-VideoControlsRoot { + position: absolute; + right: 0; + bottom: -1px; + left: 0; + width: 100%; +}