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%;
+}