@eluvio/elv-player-js 2.1.2 → 2.1.4
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/dist/.vite/manifest.json +16 -16
- package/dist/{dash.all.min-BL6tNUCs.mjs → dash.all.min-DvWZ0MqG.mjs} +1 -1
- package/dist/{dash.all.min-DSdDnxBL.js → dash.all.min-Od-SiJh8.js} +1 -1
- package/dist/elv-player-js.cjs.js +1 -1
- package/dist/elv-player-js.css +1 -1
- package/dist/elv-player-js.es.js +1 -1
- package/dist/hls-BSI-ecIH.js +40 -0
- package/dist/{hls-4X0sH40o.mjs → hls-DhmD3Age.mjs} +5730 -5078
- package/dist/{index-BTJyodYy.mjs → index-BPrOer-q.mjs} +16292 -15821
- package/dist/{index-DlJMTNdL.mjs → index-BYeoCvGG.mjs} +1 -1
- package/dist/{index-7GwGdyHP.js → index-CGKpf-bd.js} +1 -1
- package/dist/{index-BuOaL0fD.js → index-YVAzvKzL.js} +96 -95
- package/lib/player/Player.js +21 -5
- package/lib/player/ThumbnailHandler.js +214 -0
- package/lib/static/stylesheets/common.module.scss +25 -8
- package/lib/ui/Common.js +1 -1
- package/lib/ui/Components.jsx +69 -2
- package/package.json +4 -3
- package/dist/hls-AJQ_RujU.js +0 -40
package/lib/player/Player.js
CHANGED
|
@@ -4,6 +4,7 @@ import Cast from "./Cast";
|
|
|
4
4
|
import {Utils} from "@eluvio/elv-client-js";
|
|
5
5
|
import PlayerControls from "./Controls.js";
|
|
6
6
|
import {MergeDefaultParameters} from "../ui/Common";
|
|
7
|
+
import ThumbnailHandler from "./ThumbnailHandler";
|
|
7
8
|
|
|
8
9
|
const isIOS = /iPhone|iPad|iPod/i.test(navigator.userAgent);
|
|
9
10
|
|
|
@@ -247,7 +248,7 @@ export class EluvioPlayer {
|
|
|
247
248
|
}
|
|
248
249
|
}
|
|
249
250
|
|
|
250
|
-
const { playoutUrl, drms } = this.sourceOptions.playoutOptions[protocol].playoutMethods[drm];
|
|
251
|
+
const { playoutUrl, drms, thumbnailTrack } = this.sourceOptions.playoutOptions[protocol].playoutMethods[drm];
|
|
251
252
|
|
|
252
253
|
const versionHash = playoutUrl.split("/").find(segment => segment.startsWith("hq__"));
|
|
253
254
|
|
|
@@ -259,6 +260,7 @@ export class EluvioPlayer {
|
|
|
259
260
|
protocol,
|
|
260
261
|
drm,
|
|
261
262
|
playoutUrl,
|
|
263
|
+
thumbnailTrackUrl: thumbnailTrack,
|
|
262
264
|
versionHash,
|
|
263
265
|
drms,
|
|
264
266
|
availableDRMs,
|
|
@@ -449,7 +451,7 @@ export class EluvioPlayer {
|
|
|
449
451
|
this.__RegisterVideoEventListener("ended", () => this.controls && this.controls.CollectionPlayNext({autoplay: true}));
|
|
450
452
|
}
|
|
451
453
|
|
|
452
|
-
let { versionHash, playoutUrl, protocol, drm, drms, multiviewOptions, playoutParameters } = await this.__PlayoutOptions();
|
|
454
|
+
let { versionHash, playoutUrl, thumbnailTrackUrl, protocol, drm, drms, multiviewOptions, playoutParameters } = await this.__PlayoutOptions();
|
|
453
455
|
|
|
454
456
|
this.contentHash = versionHash;
|
|
455
457
|
|
|
@@ -463,6 +465,7 @@ export class EluvioPlayer {
|
|
|
463
465
|
this.authorizationToken = authorizationToken;
|
|
464
466
|
|
|
465
467
|
this.playoutUrl = playoutUrl.toString();
|
|
468
|
+
this.thumbnailTrackUrl = thumbnailTrackUrl;
|
|
466
469
|
|
|
467
470
|
if(this.castHandler) {
|
|
468
471
|
this.castHandler.SetMedia({
|
|
@@ -522,6 +525,15 @@ export class EluvioPlayer {
|
|
|
522
525
|
|
|
523
526
|
this.initialized = true;
|
|
524
527
|
this.restartParameters = undefined;
|
|
528
|
+
|
|
529
|
+
if(this.thumbnailTrackUrl) {
|
|
530
|
+
this.thumbnailHandler = new ThumbnailHandler(this.thumbnailTrackUrl);
|
|
531
|
+
this.thumbnailHandler.LoadThumbnails()
|
|
532
|
+
.then(() => {
|
|
533
|
+
this.thumbnailsLoaded = true;
|
|
534
|
+
this.__SettingsUpdate();
|
|
535
|
+
});
|
|
536
|
+
}
|
|
525
537
|
} catch (error) {
|
|
526
538
|
// If playout failed due to a permission issue, check the content to see if there is a message to display
|
|
527
539
|
let permissionErrorMessage;
|
|
@@ -803,7 +815,7 @@ export class EluvioPlayer {
|
|
|
803
815
|
clearTimeout(this.stallTimeout);
|
|
804
816
|
|
|
805
817
|
let stalledTime = this.video && this.video.currentTime;
|
|
806
|
-
setTimeout(() => {
|
|
818
|
+
this.stallTimeout = setTimeout(() => {
|
|
807
819
|
if(this.video && this.video.currentTime > stalledTime) {
|
|
808
820
|
return;
|
|
809
821
|
}
|
|
@@ -814,8 +826,8 @@ export class EluvioPlayer {
|
|
|
814
826
|
this.stallTimeout = setTimeout(() => {
|
|
815
827
|
this.Log("Buffer Stalled. Reloading player...", true);
|
|
816
828
|
this.__HardReload(error, 0, stalledTime);
|
|
817
|
-
},
|
|
818
|
-
},
|
|
829
|
+
}, 6000);
|
|
830
|
+
}, 6000);
|
|
819
831
|
}
|
|
820
832
|
} else {
|
|
821
833
|
this.__HardReload(error);
|
|
@@ -1210,6 +1222,10 @@ export class EluvioPlayer {
|
|
|
1210
1222
|
this.dashPlayer = undefined;
|
|
1211
1223
|
this.player = undefined;
|
|
1212
1224
|
this.initTimeLogged = false;
|
|
1225
|
+
this.playoutUrl = undefined;
|
|
1226
|
+
this.thumbnailTrackUrl = undefined;
|
|
1227
|
+
this.thumbnailHandler = undefined;
|
|
1228
|
+
this.thumbnailsLoaded = undefined;
|
|
1213
1229
|
this.canPlay = false;
|
|
1214
1230
|
this.isLive = false;
|
|
1215
1231
|
this.behindLiveEdge = false;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import {Utils} from "@eluvio/elv-client-js";
|
|
2
|
+
import UrlJoin from "url-join";
|
|
3
|
+
import {IntervalTree} from "node-interval-tree";
|
|
4
|
+
|
|
5
|
+
let _tagId = 1;
|
|
6
|
+
export const Cue = ({tagType, tagId, label, startTime, endTime, text, tag, ...extra}) => {
|
|
7
|
+
let content;
|
|
8
|
+
if(Array.isArray(text)) {
|
|
9
|
+
text = text.join(", ");
|
|
10
|
+
} else if(typeof text === "object") {
|
|
11
|
+
content = text;
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
text = JSON.stringify(text, null, 2);
|
|
15
|
+
} catch(error) {
|
|
16
|
+
text = "";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
tagId,
|
|
22
|
+
tagType,
|
|
23
|
+
label,
|
|
24
|
+
startTime,
|
|
25
|
+
endTime,
|
|
26
|
+
text,
|
|
27
|
+
content,
|
|
28
|
+
tag,
|
|
29
|
+
...extra
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const FormatVTTCue = ({label, cue}) => {
|
|
34
|
+
// VTT Cues are weird about being inspected and copied
|
|
35
|
+
// Manually copy all relevant values
|
|
36
|
+
const cueAttributes = [
|
|
37
|
+
"align",
|
|
38
|
+
"endTime",
|
|
39
|
+
"id",
|
|
40
|
+
"line",
|
|
41
|
+
"lineAlign",
|
|
42
|
+
"position",
|
|
43
|
+
"positionAlign",
|
|
44
|
+
"region",
|
|
45
|
+
"size",
|
|
46
|
+
"snapToLines",
|
|
47
|
+
"startTime",
|
|
48
|
+
"text",
|
|
49
|
+
"vertical"
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
const cueCopy = {};
|
|
53
|
+
cueAttributes.forEach(attr => cueCopy[attr] = cue[attr]);
|
|
54
|
+
|
|
55
|
+
const tagId = _tagId;
|
|
56
|
+
_tagId += 1;
|
|
57
|
+
|
|
58
|
+
return Cue({
|
|
59
|
+
tagId,
|
|
60
|
+
tagType: "vtt",
|
|
61
|
+
label,
|
|
62
|
+
startTime: cue.startTime,
|
|
63
|
+
endTime: cue.endTime,
|
|
64
|
+
text: cue.text,
|
|
65
|
+
tag: cueCopy
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const ParseVTTTrack = async (track) => {
|
|
70
|
+
const videoElement = document.createElement("video");
|
|
71
|
+
const trackElement = document.createElement("track");
|
|
72
|
+
|
|
73
|
+
const dataURL = "data:text/plain;base64," + Utils.B64(track.vttData);
|
|
74
|
+
|
|
75
|
+
const textTrack = trackElement.track;
|
|
76
|
+
|
|
77
|
+
videoElement.append(trackElement);
|
|
78
|
+
trackElement.src = dataURL;
|
|
79
|
+
|
|
80
|
+
textTrack.mode = "hidden";
|
|
81
|
+
|
|
82
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
83
|
+
|
|
84
|
+
let cues = {};
|
|
85
|
+
Array.from(textTrack.cues)
|
|
86
|
+
.forEach(cue => {
|
|
87
|
+
const parsedCue = FormatVTTCue({label: track.label, cue});
|
|
88
|
+
cues[parsedCue.tagId] = parsedCue;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return cues;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const CreateTrackIntervalTree = (tags, label) => {
|
|
95
|
+
const intervalTree = new IntervalTree();
|
|
96
|
+
|
|
97
|
+
Object.values(tags).forEach(tag => {
|
|
98
|
+
try {
|
|
99
|
+
intervalTree.insert({low: tag.startTime, high: tag.startTime + 1, name: tag.tagId});
|
|
100
|
+
} catch(error) {
|
|
101
|
+
// eslint-disable-next-line no-console
|
|
102
|
+
console.warn(`Invalid tag in track '${label}'`);
|
|
103
|
+
// eslint-disable-next-line no-console
|
|
104
|
+
console.warn(JSON.stringify(tag, null, 2));
|
|
105
|
+
// eslint-disable-next-line no-console
|
|
106
|
+
console.warn(error);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return intervalTree;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
class ThumbnailHandler {
|
|
114
|
+
constructor(thumbnailTrackUrl) {
|
|
115
|
+
this.thumbnailTrackUrl = thumbnailTrackUrl;
|
|
116
|
+
this.loaded = false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async LoadThumbnails() {
|
|
120
|
+
const thumbnailTrackUrl = new URL(this.thumbnailTrackUrl);
|
|
121
|
+
const authToken = thumbnailTrackUrl.searchParams.get("authorization");
|
|
122
|
+
thumbnailTrackUrl.searchParams.delete("authorization");
|
|
123
|
+
const vttData = await (await fetch(thumbnailTrackUrl, {headers: {Authorization: `Bearer ${authToken}`}})).text();
|
|
124
|
+
|
|
125
|
+
let tags = await ParseVTTTrack({label: "Thumbnails", vttData});
|
|
126
|
+
|
|
127
|
+
// Determine the maximum time between thumbnails
|
|
128
|
+
let maxInterval = 0;
|
|
129
|
+
let lastStartTime = 0;
|
|
130
|
+
Object.keys(tags || {}).forEach(key => {
|
|
131
|
+
tags[key].endTime = tags[key].startTime;
|
|
132
|
+
|
|
133
|
+
maxInterval = Math.max(maxInterval, tags[key].startTime - lastStartTime);
|
|
134
|
+
lastStartTime = tags[key].startTime;
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
let imageUrls = {};
|
|
138
|
+
Object.keys(tags).map(id => {
|
|
139
|
+
const [path, rest] = tags[id].tag.text.split("\n")[0].split("?");
|
|
140
|
+
const [query, hash] = rest.split("#");
|
|
141
|
+
const positionParams = hash.split("=")[1].split(",").map(n => parseInt(n));
|
|
142
|
+
const queryParams = new URLSearchParams(`?${query}`);
|
|
143
|
+
const url = new URL(thumbnailTrackUrl);
|
|
144
|
+
url.searchParams.set("authorization", authToken);
|
|
145
|
+
url.pathname = UrlJoin(url.pathname.split("/").slice(0, -1).join("/"), path);
|
|
146
|
+
queryParams.forEach((key, value) =>
|
|
147
|
+
url.searchParams.set(key, value)
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
tags[id].imageUrl = url.toString();
|
|
151
|
+
tags[id].thumbnailPosition = positionParams;
|
|
152
|
+
|
|
153
|
+
imageUrls[url.toString()] = true;
|
|
154
|
+
|
|
155
|
+
delete tags[id].tag.text;
|
|
156
|
+
delete tags[id].text;
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
await Promise.all(
|
|
160
|
+
Object.keys(imageUrls).map(async url => {
|
|
161
|
+
const image = new Image();
|
|
162
|
+
|
|
163
|
+
await new Promise(resolve => {
|
|
164
|
+
image.src = url;
|
|
165
|
+
image.crossOrigin = "anonymous";
|
|
166
|
+
image.onload = () => {
|
|
167
|
+
resolve();
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
imageUrls[url] = image;
|
|
172
|
+
})
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
this.maxInterval = Math.ceil(maxInterval);
|
|
176
|
+
this.thumbnailImages = imageUrls;
|
|
177
|
+
this.thumbnails = tags;
|
|
178
|
+
this.intervalTree = CreateTrackIntervalTree(tags, "Thumbnails");
|
|
179
|
+
|
|
180
|
+
this.loaded = true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
ThumbnailImage(startTime) {
|
|
184
|
+
if(!this.intervalTree) { return; }
|
|
185
|
+
|
|
186
|
+
let record = (this.intervalTree.search(startTime, startTime + this.maxInterval))[0];
|
|
187
|
+
let thumbnailIndex = record && record.name;
|
|
188
|
+
|
|
189
|
+
if(!thumbnailIndex) { return; }
|
|
190
|
+
|
|
191
|
+
const tag = this.thumbnails?.[thumbnailIndex?.toString()];
|
|
192
|
+
|
|
193
|
+
if(!tag) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if(!this.thumbnailCanvas) {
|
|
198
|
+
this.thumbnailCanvas = document.createElement("canvas");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const image = this.thumbnailImages[tag?.imageUrl];
|
|
202
|
+
|
|
203
|
+
if(image) {
|
|
204
|
+
const [x, y, w, h] = tag.thumbnailPosition;
|
|
205
|
+
this.thumbnailCanvas.height = h;
|
|
206
|
+
this.thumbnailCanvas.width = w;
|
|
207
|
+
const context = this.thumbnailCanvas.getContext("2d");
|
|
208
|
+
context.drawImage(image, x, y, w, h, 0, 0, w, h);
|
|
209
|
+
return this.thumbnailCanvas.toDataURL("image/png");
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export default ThumbnailHandler;
|
|
@@ -73,8 +73,8 @@
|
|
|
73
73
|
|
|
74
74
|
.seek-container {
|
|
75
75
|
--progress-height: 3px;
|
|
76
|
-
--progress-height-expanded:
|
|
77
|
-
--progress-height-expanded-mobile:
|
|
76
|
+
--progress-height-expanded: 12px;
|
|
77
|
+
--progress-height-expanded-mobile: 12px;
|
|
78
78
|
--color-seek-background: rgba(255, 255, 255, 10%);
|
|
79
79
|
--color-seek-buffer: rgba(255, 255, 255, 10%);
|
|
80
80
|
--color-seek-active: rgba(255, 255, 255, 50%);
|
|
@@ -93,6 +93,28 @@
|
|
|
93
93
|
transition-delay: 0.25s;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
.thumbnail {
|
|
97
|
+
animation: 0.5s fadein ease;
|
|
98
|
+
bottom: 100%;
|
|
99
|
+
color: transparent;
|
|
100
|
+
opacity: 0;
|
|
101
|
+
pointer-events: none;
|
|
102
|
+
position: absolute;
|
|
103
|
+
transition-delay: 0s;
|
|
104
|
+
user-select: none;
|
|
105
|
+
width: 250px;
|
|
106
|
+
|
|
107
|
+
&--visible {
|
|
108
|
+
opacity: 1;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
img {
|
|
112
|
+
border-radius: 5px;
|
|
113
|
+
height: auto;
|
|
114
|
+
width: 250px;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
96
118
|
&:hover,
|
|
97
119
|
&:active,
|
|
98
120
|
&:focus,
|
|
@@ -129,12 +151,6 @@
|
|
|
129
151
|
transition-delay: unset;
|
|
130
152
|
}
|
|
131
153
|
}
|
|
132
|
-
|
|
133
|
-
&:focus-visible,
|
|
134
|
-
&:has(:focus-visible),
|
|
135
|
-
&:hover {
|
|
136
|
-
filter: drop-shadow(0 0 5px var(--color-seek-active-focused));
|
|
137
|
-
}
|
|
138
154
|
}
|
|
139
155
|
|
|
140
156
|
.seek-playhead,
|
|
@@ -691,6 +707,7 @@
|
|
|
691
707
|
|
|
692
708
|
.seek-container {
|
|
693
709
|
height: calc(var(--progress-height-expanded) + 5px);
|
|
710
|
+
position: relative;
|
|
694
711
|
|
|
695
712
|
&:hover,
|
|
696
713
|
&:active,
|
package/lib/ui/Common.js
CHANGED
|
@@ -206,7 +206,7 @@ export const MergeParameters = (defaults, parameters={}, depth=1) => {
|
|
|
206
206
|
let merged = {};
|
|
207
207
|
Object.keys(defaults).forEach(key => {
|
|
208
208
|
if(key === "client") {
|
|
209
|
-
merged.client = parameters.client;
|
|
209
|
+
merged.client = parameters.client || defaults.client;
|
|
210
210
|
} else if(typeof defaults[key] === "object" && !Array.isArray(defaults[key])) {
|
|
211
211
|
merged[key] = MergeParameters(defaults[key], (parameters || {})[key], depth+1);
|
|
212
212
|
} else {
|
package/lib/ui/Components.jsx
CHANGED
|
@@ -74,11 +74,48 @@ export const UserActionIndicator = ({action}) => {
|
|
|
74
74
|
);
|
|
75
75
|
};
|
|
76
76
|
|
|
77
|
+
const Thumbnail = ({player, time, progress, videoState, visible}) => {
|
|
78
|
+
const [ref, setRef] = useState(null);
|
|
79
|
+
|
|
80
|
+
if(!player.thumbnailsLoaded) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
time = typeof time !== "undefined" ? time :
|
|
85
|
+
progress * videoState.duration;
|
|
86
|
+
|
|
87
|
+
progress = typeof progress !== "undefined" ? progress :
|
|
88
|
+
time / videoState.duration;
|
|
89
|
+
|
|
90
|
+
let maxPercent = 100;
|
|
91
|
+
if(ref) {
|
|
92
|
+
const { width } = ref.parentElement.getBoundingClientRect();
|
|
93
|
+
maxPercent = (width - 250) / width;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const thumbnailImage = player.thumbnailHandler.ThumbnailImage(time);
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<div
|
|
100
|
+
ref={setRef}
|
|
101
|
+
style={{
|
|
102
|
+
left: `${Math.min(progress, maxPercent) * 100}%`
|
|
103
|
+
}}
|
|
104
|
+
className={`${CommonStyles["thumbnail"]} ${visible ? CommonStyles["thumbnail--visible"] : ""}`}
|
|
105
|
+
>
|
|
106
|
+
<img src={thumbnailImage} alt="Thumbnail" className={CommonStyles["thumbnail__image"]} />
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
};
|
|
110
|
+
|
|
77
111
|
export const SeekBar = ({player, videoState, setRecentUserAction, className=""}) => {
|
|
78
112
|
const [currentTime, setCurrentTime] = useState(player.controls.GetCurrentTime());
|
|
79
113
|
const [bufferFraction, setBufferFraction] = useState(0);
|
|
80
114
|
const [seekKeydownHandler, setSeekKeydownHandler] = useState(undefined);
|
|
81
115
|
const [dvrEnabled, setDVREnabled] = useState(player.controls.IsDVREnabled());
|
|
116
|
+
const [hoverPosition, setHoverPosition] = useState(0);
|
|
117
|
+
const [hovering, setHovering] = useState(false);
|
|
118
|
+
const [focused, setFocused] = useState(false);
|
|
82
119
|
|
|
83
120
|
useEffect(() => {
|
|
84
121
|
setSeekKeydownHandler(SeekSliderKeyDown(player, setRecentUserAction));
|
|
@@ -103,7 +140,20 @@ export const SeekBar = ({player, videoState, setRecentUserAction, className=""})
|
|
|
103
140
|
}
|
|
104
141
|
|
|
105
142
|
return (
|
|
106
|
-
<div
|
|
143
|
+
<div
|
|
144
|
+
onMouseEnter={() => setHovering(true)}
|
|
145
|
+
onMouseLeave={() => {
|
|
146
|
+
setFocused(false);
|
|
147
|
+
setHovering(false);
|
|
148
|
+
}}
|
|
149
|
+
onFocus={() => setFocused(true)}
|
|
150
|
+
onBlur={() => setFocused(false)}
|
|
151
|
+
onMouseMove={event => {
|
|
152
|
+
const { left, width } = event.currentTarget.getBoundingClientRect();
|
|
153
|
+
setHoverPosition(event.clientX / (width - left));
|
|
154
|
+
}}
|
|
155
|
+
className={`${className} ${CommonStyles["seek-container"]} ${className}`}
|
|
156
|
+
>
|
|
107
157
|
<progress
|
|
108
158
|
max={1}
|
|
109
159
|
value={player.casting ? 0 : bufferFraction}
|
|
@@ -121,10 +171,27 @@ export const SeekBar = ({player, videoState, setRecentUserAction, className=""})
|
|
|
121
171
|
max={1}
|
|
122
172
|
step={0.00001}
|
|
123
173
|
value={currentTime / videoState.duration || 0}
|
|
124
|
-
onInput={event =>
|
|
174
|
+
onInput={event => {
|
|
175
|
+
player.controls.Seek({fraction: event.currentTarget.value});
|
|
176
|
+
}}
|
|
177
|
+
onTouchStart={() => setHovering(true)}
|
|
178
|
+
onTouchMove={event => {
|
|
179
|
+
const { left, width } = event.currentTarget.getBoundingClientRect();
|
|
180
|
+
const progress = event.touches[0].pageX / (width - left);
|
|
181
|
+
setHoverPosition(progress);
|
|
182
|
+
|
|
183
|
+
player.controls.Seek({fraction: progress});
|
|
184
|
+
}}
|
|
185
|
+
onTouchEnd={() => setHovering(false)}
|
|
125
186
|
onKeyDown={seekKeydownHandler}
|
|
126
187
|
className={CommonStyles["seek-input"]}
|
|
127
188
|
/>
|
|
189
|
+
<Thumbnail
|
|
190
|
+
player={player}
|
|
191
|
+
progress={focused ? currentTime / videoState.duration : hoverPosition}
|
|
192
|
+
videoState={videoState}
|
|
193
|
+
visible={hovering || focused}
|
|
194
|
+
/>
|
|
128
195
|
</div>
|
|
129
196
|
);
|
|
130
197
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eluvio/elv-player-js",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.4",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/elv-player-js.es.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,11 +36,12 @@
|
|
|
36
36
|
"package-lock.json"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@eluvio/elv-client-js": "^4.2.
|
|
39
|
+
"@eluvio/elv-client-js": "^4.2.5",
|
|
40
40
|
"dashjs": "git+https://github.com/elv-zenia/dash.js.git#text-track-fix",
|
|
41
41
|
"focus-visible": "^5.2.0",
|
|
42
|
-
"hls.js": "1.6.
|
|
42
|
+
"hls.js": "1.6.13",
|
|
43
43
|
"mux-embed": "^5.9.0",
|
|
44
|
+
"node-interval-tree": "^2.1.2",
|
|
44
45
|
"react": "^18.2.0",
|
|
45
46
|
"react-dom": "^18.2.0",
|
|
46
47
|
"resize-observer-polyfill": "^1.5.1",
|