bitmovin-player-ui 3.75.0 → 3.76.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/artifact/artifact.tar.gz +0 -0
- package/dist/css/bitmovinplayer-ui.css +8 -6
- package/dist/css/bitmovinplayer-ui.min.css +1 -1
- package/dist/js/bitmovinplayer-ui.js +52 -21
- package/dist/js/bitmovinplayer-ui.min.js +1 -1
- package/dist/js/bitmovinplayer-ui.min.js.map +1 -1
- package/dist/js/framework/components/seekbar.d.ts +4 -1
- package/dist/js/framework/components/seekbar.js +19 -9
- package/dist/js/framework/components/seekbarlabel.d.ts +3 -0
- package/dist/js/framework/components/seekbarlabel.js +31 -10
- package/dist/js/framework/main.js +1 -1
- package/package.json +1 -1
- package/spec/components/seekbar.spec.ts +4 -4
- package/spec/components/seekbarlabel.spec.ts +94 -0
- package/src/scss/skin-modern/components/_seekbarlabel.scss +17 -16
- package/src/ts/components/seekbar.ts +25 -9
- package/src/ts/components/seekbarlabel.ts +38 -9
|
@@ -93,7 +93,9 @@ export declare class SeekBar extends Component<SeekBarConfig> {
|
|
|
93
93
|
private label;
|
|
94
94
|
private seekBarMarkersContainer;
|
|
95
95
|
private timelineMarkersHandler;
|
|
96
|
+
private uiBoundingRect;
|
|
96
97
|
private player;
|
|
98
|
+
private uiManager;
|
|
97
99
|
protected seekBarType: SeekBarType;
|
|
98
100
|
protected isUiShown: boolean;
|
|
99
101
|
/**
|
|
@@ -206,7 +208,8 @@ export declare class SeekBar extends Component<SeekBarConfig> {
|
|
|
206
208
|
*/
|
|
207
209
|
getLabel(): SeekBarLabel | null;
|
|
208
210
|
protected onSeekEvent(): void;
|
|
209
|
-
|
|
211
|
+
private updateLabelPosition;
|
|
212
|
+
protected onSeekPreviewEvent(percentage: number, targetOffsetPx: number, scrubbing: boolean): void;
|
|
210
213
|
protected onSeekedEvent(percentage: number): void;
|
|
211
214
|
/**
|
|
212
215
|
* Gets the event that is fired when a scrubbing seek operation is started.
|
|
@@ -92,6 +92,12 @@ var SeekBar = exports.SeekBar = /** @class */ (function (_super) {
|
|
|
92
92
|
_this.player.seek(targetPlaybackPosition, 'ui');
|
|
93
93
|
}
|
|
94
94
|
};
|
|
95
|
+
_this.updateLabelPosition = function (pixelPosition) {
|
|
96
|
+
if (!_this.uiBoundingRect) {
|
|
97
|
+
_this.uiBoundingRect = _this.uiManager.getUI().getDomElement().get(0).getBoundingClientRect();
|
|
98
|
+
}
|
|
99
|
+
_this.label.setPositionInBounds(pixelPosition, _this.uiBoundingRect);
|
|
100
|
+
};
|
|
95
101
|
var keyStepIncrements = _this.config.keyStepIncrements || {
|
|
96
102
|
leftRight: 1,
|
|
97
103
|
upDown: 5,
|
|
@@ -154,6 +160,7 @@ var SeekBar = exports.SeekBar = /** @class */ (function (_super) {
|
|
|
154
160
|
if (configureSeek === void 0) { configureSeek = true; }
|
|
155
161
|
_super.prototype.configure.call(this, player, uimanager);
|
|
156
162
|
this.player = player;
|
|
163
|
+
this.uiManager = uimanager;
|
|
157
164
|
// Apply scaling transform to the backdrop bar to have all bars rendered similarly
|
|
158
165
|
// (the call must be up here to be executed for the volume slider as well)
|
|
159
166
|
this.setPosition(this.seekBarBackdrop, 100);
|
|
@@ -354,6 +361,7 @@ var SeekBar = exports.SeekBar = /** @class */ (function (_super) {
|
|
|
354
361
|
// is positioned absolutely and must therefore be updated when the size of the seekbar changes.
|
|
355
362
|
player.on(player.exports.PlayerEvent.PlayerResized, function () {
|
|
356
363
|
_this.refreshPlaybackPosition();
|
|
364
|
+
_this.uiBoundingRect = _this.uiManager.getUI().getDomElement().get(0).getBoundingClientRect();
|
|
357
365
|
});
|
|
358
366
|
// Additionally, when this code is called, the seekbar is not part of the UI yet and therefore does not have a size,
|
|
359
367
|
// resulting in a wrong initial position of the marker. Refreshing it once the UI is configured solved this issue.
|
|
@@ -551,10 +559,12 @@ var SeekBar = exports.SeekBar = /** @class */ (function (_super) {
|
|
|
551
559
|
if (_this.player.vr != null) {
|
|
552
560
|
e.stopPropagation();
|
|
553
561
|
}
|
|
554
|
-
var
|
|
562
|
+
var offset = _this.getOffset(e);
|
|
563
|
+
var targetPercentage = 100 * offset;
|
|
564
|
+
var seekPositionPx = offset * _this.seekBar.width();
|
|
555
565
|
_this.setSeekPosition(targetPercentage);
|
|
556
566
|
_this.setPlaybackPosition(targetPercentage);
|
|
557
|
-
_this.onSeekPreviewEvent(targetPercentage, true);
|
|
567
|
+
_this.onSeekPreviewEvent(targetPercentage, seekPositionPx, true);
|
|
558
568
|
};
|
|
559
569
|
var mouseTouchUpHandler = function (e) {
|
|
560
570
|
var _a;
|
|
@@ -599,9 +609,11 @@ var SeekBar = exports.SeekBar = /** @class */ (function (_super) {
|
|
|
599
609
|
if (seeking) {
|
|
600
610
|
mouseTouchMoveHandler(e);
|
|
601
611
|
}
|
|
602
|
-
var
|
|
603
|
-
|
|
604
|
-
_this.
|
|
612
|
+
var offset = _this.getOffset(e);
|
|
613
|
+
var seekPositionPercentage = 100 * offset;
|
|
614
|
+
var seekPositionPx = offset * _this.seekBar.width();
|
|
615
|
+
_this.setSeekPosition(seekPositionPercentage);
|
|
616
|
+
_this.onSeekPreviewEvent(seekPositionPercentage, seekPositionPx, false);
|
|
605
617
|
if (_this.hasLabel() && _this.getLabel().isHidden()) {
|
|
606
618
|
_this.getLabel().show();
|
|
607
619
|
}
|
|
@@ -815,7 +827,7 @@ var SeekBar = exports.SeekBar = /** @class */ (function (_super) {
|
|
|
815
827
|
SeekBar.prototype.onSeekEvent = function () {
|
|
816
828
|
this.seekBarEvents.onSeek.dispatch(this);
|
|
817
829
|
};
|
|
818
|
-
SeekBar.prototype.onSeekPreviewEvent = function (percentage, scrubbing) {
|
|
830
|
+
SeekBar.prototype.onSeekPreviewEvent = function (percentage, targetOffsetPx, scrubbing) {
|
|
819
831
|
var snappedMarker = this.timelineMarkersHandler && this.timelineMarkersHandler.getMarkerAtPosition(percentage);
|
|
820
832
|
var seekPositionPercentage = percentage;
|
|
821
833
|
if (snappedMarker) {
|
|
@@ -837,9 +849,7 @@ var SeekBar = exports.SeekBar = /** @class */ (function (_super) {
|
|
|
837
849
|
}
|
|
838
850
|
}
|
|
839
851
|
if (this.label) {
|
|
840
|
-
this.
|
|
841
|
-
'left': seekPositionPercentage + '%',
|
|
842
|
-
});
|
|
852
|
+
this.updateLabelPosition(targetOffsetPx);
|
|
843
853
|
}
|
|
844
854
|
this.seekBarEvents.onSeekPreview.dispatch(this, {
|
|
845
855
|
scrubbing: scrubbing,
|
|
@@ -22,9 +22,12 @@ export declare class SeekBarLabel extends Container<SeekBarLabelConfig> {
|
|
|
22
22
|
private appliedMarkerCssClasses;
|
|
23
23
|
private player;
|
|
24
24
|
private uiManager;
|
|
25
|
+
private readonly container;
|
|
26
|
+
private readonly caret;
|
|
25
27
|
constructor(config?: SeekBarLabelConfig);
|
|
26
28
|
configure(player: PlayerAPI, uimanager: UIInstanceManager): void;
|
|
27
29
|
private handleSeekPreview;
|
|
30
|
+
setPositionInBounds(seekPositionPx: number, bounds: DOMRect): void;
|
|
28
31
|
/**
|
|
29
32
|
* Sets arbitrary text on the label.
|
|
30
33
|
* @param text the text to show on the label
|
|
@@ -78,18 +78,20 @@ var SeekBarLabel = /** @class */ (function (_super) {
|
|
|
78
78
|
_this.titleLabel = new label_1.Label({ cssClasses: ['seekbar-label-title'] });
|
|
79
79
|
_this.thumbnail = new component_1.Component({ cssClasses: ['seekbar-thumbnail'], role: 'img' });
|
|
80
80
|
_this.thumbnailImageLoader = new imageloader_1.ImageLoader();
|
|
81
|
+
_this.container = new container_1.Container({
|
|
82
|
+
components: [
|
|
83
|
+
_this.thumbnail,
|
|
84
|
+
new container_1.Container({
|
|
85
|
+
components: [_this.titleLabel, _this.timeLabel],
|
|
86
|
+
cssClass: 'seekbar-label-metadata',
|
|
87
|
+
}),
|
|
88
|
+
],
|
|
89
|
+
cssClass: 'seekbar-label-inner',
|
|
90
|
+
});
|
|
91
|
+
_this.caret = new label_1.Label({ cssClasses: ['seekbar-label-caret'] });
|
|
81
92
|
_this.config = _this.mergeConfig(config, {
|
|
82
93
|
cssClass: 'ui-seekbar-label',
|
|
83
|
-
components: [
|
|
84
|
-
components: [
|
|
85
|
-
_this.thumbnail,
|
|
86
|
-
new container_1.Container({
|
|
87
|
-
components: [_this.titleLabel, _this.timeLabel],
|
|
88
|
-
cssClass: 'seekbar-label-metadata',
|
|
89
|
-
})
|
|
90
|
-
],
|
|
91
|
-
cssClass: 'seekbar-label-inner',
|
|
92
|
-
})],
|
|
94
|
+
components: [_this.container, _this.caret],
|
|
93
95
|
hidden: true,
|
|
94
96
|
}, _this.config);
|
|
95
97
|
return _this;
|
|
@@ -111,6 +113,25 @@ var SeekBarLabel = /** @class */ (function (_super) {
|
|
|
111
113
|
uimanager.getConfig().events.onUpdated.subscribe(init);
|
|
112
114
|
init();
|
|
113
115
|
};
|
|
116
|
+
SeekBarLabel.prototype.setPositionInBounds = function (seekPositionPx, bounds) {
|
|
117
|
+
this.getDomElement().css('left', seekPositionPx + 'px');
|
|
118
|
+
// Check parent container as it has a padding that needs to be considered
|
|
119
|
+
var labelBounding = this.container.getDomElement().get(0).parentElement.getBoundingClientRect();
|
|
120
|
+
var preventOverflowOffset = 0;
|
|
121
|
+
if (labelBounding.right > bounds.right) {
|
|
122
|
+
preventOverflowOffset = labelBounding.right - bounds.right;
|
|
123
|
+
}
|
|
124
|
+
else if (labelBounding.left < bounds.left) {
|
|
125
|
+
preventOverflowOffset = labelBounding.left - bounds.left;
|
|
126
|
+
}
|
|
127
|
+
if (preventOverflowOffset !== 0) {
|
|
128
|
+
this.getDomElement().css('left', seekPositionPx - preventOverflowOffset + 'px');
|
|
129
|
+
this.caret.getDomElement().css('transform', "translateX(".concat(preventOverflowOffset, "px)"));
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
this.caret.getDomElement().css('transform', null);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
114
135
|
/**
|
|
115
136
|
* Sets arbitrary text on the label.
|
|
116
137
|
* @param text the text to show on the label
|
|
@@ -16,7 +16,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
exports.ClickOverlay = exports.VolumeControlButton = exports.TitleBar = exports.SubtitleSelectBox = exports.SubtitleOverlay = exports.SeekBarLabel = exports.RecommendationOverlay = exports.ErrorMessageOverlay = exports.Component = exports.CastToggleButton = exports.CastStatusOverlay = exports.AudioTrackSelectBox = exports.AudioQualitySelectBox = exports.Label = exports.Container = exports.UIContainer = exports.Watermark = exports.VRToggleButton = exports.VolumeToggleButton = exports.VideoQualitySelectBox = exports.ToggleButton = exports.SettingsToggleButton = exports.SettingsPanel = exports.ItemSelectionList = exports.SelectBox = exports.SeekBar = exports.PlaybackToggleButton = exports.PlaybackTimeLabelMode = exports.PlaybackTimeLabel = exports.HugePlaybackToggleButton = exports.FullscreenToggleButton = exports.ControlBar = exports.Button = exports.ListOrientation = exports.ListNavigationGroup = exports.RootNavigationGroup = exports.NavigationGroup = exports.SpatialNavigation = exports.I18n = exports.i18n = exports.ErrorUtils = exports.StorageUtils = exports.BrowserUtils = exports.UIUtils = exports.PlayerUtils = exports.StringUtils = exports.ArrayUtils = exports.DemoFactory = exports.UIFactory = exports.version = void 0;
|
|
18
18
|
exports.ListSelector = exports.QuickSeekButton = exports.ReplayButton = exports.SettingsPanelItem = exports.SubtitleSettingsPanelPage = exports.SettingsPanelPageOpenButton = exports.SettingsPanelPageBackButton = exports.SettingsPanelPage = exports.AudioTrackListBox = exports.SubtitleListBox = exports.ListBox = exports.SubtitleSettingsResetButton = exports.WindowOpacitySelectBox = exports.WindowColorSelectBox = exports.SubtitleSettingsLabel = exports.SubtitleSettingSelectBox = exports.FontSizeSelectBox = exports.FontOpacitySelectBox = exports.FontFamilySelectBox = exports.FontColorSelectBox = exports.CharacterEdgeSelectBox = exports.BackgroundOpacitySelectBox = exports.BackgroundColorSelectBox = exports.Spacer = exports.PictureInPictureToggleButton = exports.VolumeSlider = exports.AirPlayToggleButton = exports.MetadataLabelContent = exports.MetadataLabel = exports.CloseButton = exports.PlaybackToggleOverlay = exports.CastUIContainer = exports.BufferingOverlay = exports.HugeReplayButton = exports.PlaybackSpeedSelectBox = exports.AdClickOverlay = exports.AdMessageLabel = exports.AdSkipButton = void 0;
|
|
19
|
-
exports.version = '3.
|
|
19
|
+
exports.version = '3.76.0';
|
|
20
20
|
// Management
|
|
21
21
|
__exportStar(require("./uimanager"), exports);
|
|
22
22
|
__exportStar(require("./uiconfig"), exports);
|
package/package.json
CHANGED
|
@@ -114,7 +114,7 @@ describe('SeekBar', () => {
|
|
|
114
114
|
|
|
115
115
|
jest.spyOn(playerMock, 'getSeekableRange').mockImplementation(() => ({start: 26, end: 30}));
|
|
116
116
|
|
|
117
|
-
seekbar['onSeekPreviewEvent'](40, true)
|
|
117
|
+
seekbar['onSeekPreviewEvent'](40, 100, true);
|
|
118
118
|
|
|
119
119
|
playerMock.eventEmitter.fireSegmentRequestFinished();
|
|
120
120
|
|
|
@@ -127,19 +127,19 @@ describe('SeekBar', () => {
|
|
|
127
127
|
it('will update the scrubber location after a successful segment request download and the user is not scrubbing', () => {
|
|
128
128
|
jest.spyOn(playerMock, 'getSeekableRange').mockImplementation(() => ({start: 26, end: 30}));
|
|
129
129
|
|
|
130
|
-
seekbar['onSeekPreviewEvent'](18, false)
|
|
130
|
+
seekbar['onSeekPreviewEvent'](18, 100, false);
|
|
131
131
|
|
|
132
132
|
playerMock.eventEmitter.fireSegmentRequestFinished();
|
|
133
133
|
|
|
134
134
|
expect(setPlaybackPositionSpy).toHaveBeenLastCalledWith(18);
|
|
135
|
-
expect(setBufferPositionSpy).toHaveBeenLastCalledWith(18)
|
|
135
|
+
expect(setBufferPositionSpy).toHaveBeenLastCalledWith(18);
|
|
136
136
|
});
|
|
137
137
|
});
|
|
138
138
|
});
|
|
139
139
|
|
|
140
140
|
describe('group playback', () => {
|
|
141
141
|
beforeEach(() => {
|
|
142
|
-
jest.spyOn(playerMock, 'getDuration').mockReturnValue(0)
|
|
142
|
+
jest.spyOn(playerMock, 'getDuration').mockReturnValue(0);
|
|
143
143
|
seekbar.configure(playerMock, uiInstanceManagerMock);
|
|
144
144
|
});
|
|
145
145
|
|
|
@@ -2,6 +2,7 @@ import { MockHelper, TestingPlayerAPI } from '../helper/MockHelper';
|
|
|
2
2
|
import { UIInstanceManager } from '../../src/ts/uimanager';
|
|
3
3
|
import { SeekBarLabel } from '../../src/ts/components/seekbarlabel';
|
|
4
4
|
import { SeekPreviewEventArgs } from '../../src/ts/components/seekbar';
|
|
5
|
+
import { DOM } from '../../src/ts/dom';
|
|
5
6
|
|
|
6
7
|
let playerMock: TestingPlayerAPI;
|
|
7
8
|
let uiInstanceManagerMock: UIInstanceManager;
|
|
@@ -94,4 +95,97 @@ describe('SeekBarLabel', () => {
|
|
|
94
95
|
});
|
|
95
96
|
});
|
|
96
97
|
});
|
|
98
|
+
|
|
99
|
+
describe("calculates correct values for thumbnail positioning", () => {
|
|
100
|
+
const uiContainerBoundingRect = {
|
|
101
|
+
x: 200,
|
|
102
|
+
y: 150,
|
|
103
|
+
width: 1600,
|
|
104
|
+
height: 900,
|
|
105
|
+
top: 150,
|
|
106
|
+
right: 1800,
|
|
107
|
+
bottom: 1050,
|
|
108
|
+
left: 200,
|
|
109
|
+
} as DOMRect;
|
|
110
|
+
|
|
111
|
+
let containerGetDomElementMock: () => jest.Mocked<DOM>;
|
|
112
|
+
let caretGetDomElementMock: () => jest.Mocked<DOM>;
|
|
113
|
+
|
|
114
|
+
beforeEach(() => {
|
|
115
|
+
containerGetDomElementMock = jest
|
|
116
|
+
.fn()
|
|
117
|
+
.mockReturnValue(MockHelper.generateDOMMock());
|
|
118
|
+
|
|
119
|
+
containerGetDomElementMock().get = jest.fn().mockReturnValue({
|
|
120
|
+
parentElement: jest.fn().mockReturnValue({
|
|
121
|
+
getBoundingClientRect: jest.fn(),
|
|
122
|
+
}),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
caretGetDomElementMock = jest.fn().mockReturnValue(MockHelper.generateDOMMock());
|
|
126
|
+
|
|
127
|
+
seekbarLabel["container"].getDomElement = containerGetDomElementMock;
|
|
128
|
+
seekbarLabel["caret"].getDomElement = caretGetDomElementMock;
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("when thumbnail within UI container bounds", () => {
|
|
132
|
+
const labelRect = {
|
|
133
|
+
x: 400,
|
|
134
|
+
y: 700,
|
|
135
|
+
width: 200,
|
|
136
|
+
height: 120,
|
|
137
|
+
top: 700,
|
|
138
|
+
right: 600,
|
|
139
|
+
bottom: 820,
|
|
140
|
+
left: 400,
|
|
141
|
+
} as DOMRect;
|
|
142
|
+
|
|
143
|
+
containerGetDomElementMock().get(0).parentElement!.getBoundingClientRect =
|
|
144
|
+
jest.fn().mockReturnValue(labelRect);
|
|
145
|
+
|
|
146
|
+
seekbarLabel.setPositionInBounds(100, uiContainerBoundingRect);
|
|
147
|
+
|
|
148
|
+
expect(caretGetDomElementMock().css).toHaveBeenCalledWith('transform', null);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("when thumbnail would overflow UI container leftside", () => {
|
|
152
|
+
const labelRect = {
|
|
153
|
+
x: 180,
|
|
154
|
+
y: 700,
|
|
155
|
+
width: 200,
|
|
156
|
+
height: 120,
|
|
157
|
+
top: 700,
|
|
158
|
+
right: 380,
|
|
159
|
+
bottom: 820,
|
|
160
|
+
left: 180,
|
|
161
|
+
} as DOMRect;
|
|
162
|
+
|
|
163
|
+
containerGetDomElementMock().get(0).parentElement!.getBoundingClientRect =
|
|
164
|
+
jest.fn().mockReturnValue(labelRect);
|
|
165
|
+
|
|
166
|
+
seekbarLabel.setPositionInBounds(100, uiContainerBoundingRect);
|
|
167
|
+
|
|
168
|
+
expect(seekbarLabel.getDomElement().css).toHaveBeenCalledWith('left', '120px');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("when thumbnail would overflow UI container rightside", () => {
|
|
172
|
+
const labelRect = {
|
|
173
|
+
x: 1650,
|
|
174
|
+
y: 700,
|
|
175
|
+
width: 200,
|
|
176
|
+
height: 120,
|
|
177
|
+
top: 700,
|
|
178
|
+
right: 1850,
|
|
179
|
+
bottom: 820,
|
|
180
|
+
left: 1650,
|
|
181
|
+
} as DOMRect;
|
|
182
|
+
|
|
183
|
+
containerGetDomElementMock().get(0).parentElement!.getBoundingClientRect =
|
|
184
|
+
jest.fn().mockReturnValue(labelRect);
|
|
185
|
+
|
|
186
|
+
seekbarLabel.setPositionInBounds(100, uiContainerBoundingRect);
|
|
187
|
+
|
|
188
|
+
expect(seekbarLabel.getDomElement().css).toHaveBeenCalledWith('left', '50px');
|
|
189
|
+
});
|
|
190
|
+
});
|
|
97
191
|
});
|
|
@@ -21,27 +21,28 @@
|
|
|
21
21
|
|
|
22
22
|
> .#{$prefix}-container-wrapper {
|
|
23
23
|
@extend %center-on-left-edge;
|
|
24
|
+
|
|
25
|
+
padding-left: 1em;
|
|
26
|
+
padding-right: 1em;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// bottom arrow from http://www.cssarrowplease.com/
|
|
30
|
+
.#{$prefix}-seekbar-label-caret {
|
|
31
|
+
border: solid transparent;
|
|
32
|
+
border-color: transparent;
|
|
33
|
+
border-top-color: $color-primary;
|
|
34
|
+
border-width: .5em;
|
|
35
|
+
height: 0;
|
|
36
|
+
margin-left: -.5em;
|
|
37
|
+
pointer-events: none;
|
|
38
|
+
position: absolute;
|
|
39
|
+
top: 100%;
|
|
40
|
+
width: 0;
|
|
24
41
|
}
|
|
25
42
|
|
|
26
43
|
.#{$prefix}-seekbar-label-inner {
|
|
27
44
|
border-bottom: .2em solid $color-primary;
|
|
28
45
|
|
|
29
|
-
// bottom arrow from http://www.cssarrowplease.com/
|
|
30
|
-
&::after {
|
|
31
|
-
border: solid transparent;
|
|
32
|
-
border-color: transparent;
|
|
33
|
-
border-top-color: $color-primary;
|
|
34
|
-
border-width: .5em;
|
|
35
|
-
content: ' ';
|
|
36
|
-
height: 0;
|
|
37
|
-
left: 50%;
|
|
38
|
-
margin-left: -.5em;
|
|
39
|
-
pointer-events: none;
|
|
40
|
-
position: absolute;
|
|
41
|
-
top: 100%;
|
|
42
|
-
width: 0;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
46
|
> .#{$prefix}-container-wrapper {
|
|
46
47
|
position: relative;
|
|
47
48
|
|
|
@@ -114,7 +114,10 @@ export class SeekBar extends Component<SeekBarConfig> {
|
|
|
114
114
|
private seekBarMarkersContainer: DOM;
|
|
115
115
|
private timelineMarkersHandler: TimelineMarkersHandler;
|
|
116
116
|
|
|
117
|
+
private uiBoundingRect: DOMRect;
|
|
118
|
+
|
|
117
119
|
private player: PlayerAPI;
|
|
120
|
+
private uiManager: UIInstanceManager;
|
|
118
121
|
|
|
119
122
|
protected seekBarType: SeekBarType;
|
|
120
123
|
|
|
@@ -220,6 +223,7 @@ export class SeekBar extends Component<SeekBarConfig> {
|
|
|
220
223
|
super.configure(player, uimanager);
|
|
221
224
|
|
|
222
225
|
this.player = player;
|
|
226
|
+
this.uiManager = uimanager;
|
|
223
227
|
|
|
224
228
|
// Apply scaling transform to the backdrop bar to have all bars rendered similarly
|
|
225
229
|
// (the call must be up here to be executed for the volume slider as well)
|
|
@@ -453,6 +457,7 @@ export class SeekBar extends Component<SeekBarConfig> {
|
|
|
453
457
|
// is positioned absolutely and must therefore be updated when the size of the seekbar changes.
|
|
454
458
|
player.on(player.exports.PlayerEvent.PlayerResized, () => {
|
|
455
459
|
this.refreshPlaybackPosition();
|
|
460
|
+
this.uiBoundingRect = this.uiManager.getUI().getDomElement().get(0).getBoundingClientRect();
|
|
456
461
|
});
|
|
457
462
|
// Additionally, when this code is called, the seekbar is not part of the UI yet and therefore does not have a size,
|
|
458
463
|
// resulting in a wrong initial position of the marker. Refreshing it once the UI is configured solved this issue.
|
|
@@ -716,10 +721,13 @@ export class SeekBar extends Component<SeekBarConfig> {
|
|
|
716
721
|
e.stopPropagation();
|
|
717
722
|
}
|
|
718
723
|
|
|
719
|
-
|
|
724
|
+
const offset = this.getOffset(e);
|
|
725
|
+
const targetPercentage = 100 * offset;
|
|
726
|
+
const seekPositionPx = offset * this.seekBar.width();
|
|
727
|
+
|
|
720
728
|
this.setSeekPosition(targetPercentage);
|
|
721
729
|
this.setPlaybackPosition(targetPercentage);
|
|
722
|
-
this.onSeekPreviewEvent(targetPercentage, true);
|
|
730
|
+
this.onSeekPreviewEvent(targetPercentage, seekPositionPx, true);
|
|
723
731
|
};
|
|
724
732
|
|
|
725
733
|
let mouseTouchUpHandler = (e: MouseEvent | TouchEvent) => {
|
|
@@ -777,10 +785,12 @@ export class SeekBar extends Component<SeekBarConfig> {
|
|
|
777
785
|
mouseTouchMoveHandler(e);
|
|
778
786
|
}
|
|
779
787
|
|
|
780
|
-
|
|
781
|
-
|
|
788
|
+
const offset = this.getOffset(e);
|
|
789
|
+
const seekPositionPercentage = 100 * offset;
|
|
790
|
+
const seekPositionPx = offset * this.seekBar.width();
|
|
782
791
|
|
|
783
|
-
this.
|
|
792
|
+
this.setSeekPosition(seekPositionPercentage);
|
|
793
|
+
this.onSeekPreviewEvent(seekPositionPercentage, seekPositionPx, false);
|
|
784
794
|
|
|
785
795
|
if (this.hasLabel() && this.getLabel().isHidden()) {
|
|
786
796
|
this.getLabel().show();
|
|
@@ -1020,7 +1030,15 @@ export class SeekBar extends Component<SeekBarConfig> {
|
|
|
1020
1030
|
this.seekBarEvents.onSeek.dispatch(this);
|
|
1021
1031
|
}
|
|
1022
1032
|
|
|
1023
|
-
|
|
1033
|
+
private updateLabelPosition = (pixelPosition: number) => {
|
|
1034
|
+
if (!this.uiBoundingRect) {
|
|
1035
|
+
this.uiBoundingRect = this.uiManager.getUI().getDomElement().get(0).getBoundingClientRect();
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
this.label.setPositionInBounds(pixelPosition, this.uiBoundingRect);
|
|
1039
|
+
};
|
|
1040
|
+
|
|
1041
|
+
protected onSeekPreviewEvent(percentage: number, targetOffsetPx: number, scrubbing: boolean) {
|
|
1024
1042
|
let snappedMarker = this.timelineMarkersHandler && this.timelineMarkersHandler.getMarkerAtPosition(percentage);
|
|
1025
1043
|
|
|
1026
1044
|
let seekPositionPercentage = percentage;
|
|
@@ -1043,9 +1061,7 @@ export class SeekBar extends Component<SeekBarConfig> {
|
|
|
1043
1061
|
}
|
|
1044
1062
|
|
|
1045
1063
|
if (this.label) {
|
|
1046
|
-
this.
|
|
1047
|
-
'left': seekPositionPercentage + '%',
|
|
1048
|
-
});
|
|
1064
|
+
this.updateLabelPosition(targetOffsetPx);
|
|
1049
1065
|
}
|
|
1050
1066
|
|
|
1051
1067
|
this.seekBarEvents.onSeekPreview.dispatch(this, {
|
|
@@ -36,6 +36,8 @@ export class SeekBarLabel extends Container<SeekBarLabelConfig> {
|
|
|
36
36
|
private appliedMarkerCssClasses: string[] = [];
|
|
37
37
|
private player: PlayerAPI;
|
|
38
38
|
private uiManager: UIInstanceManager;
|
|
39
|
+
private readonly container: Container<ContainerConfig>;
|
|
40
|
+
private readonly caret: Label<LabelConfig>;
|
|
39
41
|
|
|
40
42
|
constructor(config: SeekBarLabelConfig = {}) {
|
|
41
43
|
super(config);
|
|
@@ -45,17 +47,22 @@ export class SeekBarLabel extends Container<SeekBarLabelConfig> {
|
|
|
45
47
|
this.thumbnail = new Component({ cssClasses: ['seekbar-thumbnail'], role: 'img' });
|
|
46
48
|
this.thumbnailImageLoader = new ImageLoader();
|
|
47
49
|
|
|
50
|
+
this.container = new Container({
|
|
51
|
+
components: [
|
|
52
|
+
this.thumbnail,
|
|
53
|
+
new Container({
|
|
54
|
+
components: [this.titleLabel, this.timeLabel],
|
|
55
|
+
cssClass: 'seekbar-label-metadata',
|
|
56
|
+
}),
|
|
57
|
+
],
|
|
58
|
+
cssClass: 'seekbar-label-inner',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
this.caret = new Label({ cssClasses: ['seekbar-label-caret'] });
|
|
62
|
+
|
|
48
63
|
this.config = this.mergeConfig(config, {
|
|
49
64
|
cssClass: 'ui-seekbar-label',
|
|
50
|
-
components: [
|
|
51
|
-
components: [
|
|
52
|
-
this.thumbnail,
|
|
53
|
-
new Container({
|
|
54
|
-
components: [this.titleLabel, this.timeLabel],
|
|
55
|
-
cssClass: 'seekbar-label-metadata',
|
|
56
|
-
})],
|
|
57
|
-
cssClass: 'seekbar-label-inner',
|
|
58
|
-
})],
|
|
65
|
+
components: [this.container, this.caret],
|
|
59
66
|
hidden: true,
|
|
60
67
|
}, this.config);
|
|
61
68
|
}
|
|
@@ -129,6 +136,28 @@ export class SeekBarLabel extends Container<SeekBarLabelConfig> {
|
|
|
129
136
|
}
|
|
130
137
|
};
|
|
131
138
|
|
|
139
|
+
public setPositionInBounds(seekPositionPx: number, bounds: DOMRect) {
|
|
140
|
+
this.getDomElement().css('left', seekPositionPx + 'px');
|
|
141
|
+
|
|
142
|
+
// Check parent container as it has a padding that needs to be considered
|
|
143
|
+
const labelBounding = this.container.getDomElement().get(0).parentElement.getBoundingClientRect();
|
|
144
|
+
|
|
145
|
+
let preventOverflowOffset = 0;
|
|
146
|
+
if (labelBounding.right > bounds.right) {
|
|
147
|
+
preventOverflowOffset = labelBounding.right - bounds.right;
|
|
148
|
+
} else if (labelBounding.left < bounds.left) {
|
|
149
|
+
preventOverflowOffset = labelBounding.left - bounds.left;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (preventOverflowOffset !== 0) {
|
|
153
|
+
this.getDomElement().css('left', seekPositionPx - preventOverflowOffset + 'px');
|
|
154
|
+
|
|
155
|
+
this.caret.getDomElement().css('transform', `translateX(${preventOverflowOffset}px)`);
|
|
156
|
+
} else {
|
|
157
|
+
this.caret.getDomElement().css('transform', null);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
132
161
|
/**
|
|
133
162
|
* Sets arbitrary text on the label.
|
|
134
163
|
* @param text the text to show on the label
|