eb-player 2.2.0 → 2.3.1
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/build/eb-player.css +20 -0
- package/dist/build/ebplayer.bundle.js +205 -8
- package/dist/build/ebplayer.bundle.js.map +1 -1
- package/dist/build/types/src/core/lifecycle.d.ts +9 -0
- package/dist/build/types/src/core/lifecycle.d.ts.map +1 -1
- package/dist/build/types/src/skin/controls/seekbar.d.ts.map +1 -1
- package/dist/eb-player.css +20 -0
- package/package.json +3 -3
package/dist/build/eb-player.css
CHANGED
|
@@ -3341,6 +3341,26 @@
|
|
|
3341
3341
|
flex-direction: row-reverse;
|
|
3342
3342
|
}
|
|
3343
3343
|
|
|
3344
|
+
[dir="rtl"] .eb-seekbar-buffered,
|
|
3345
|
+
[dir="rtl"] .eb-seekbar-progress {
|
|
3346
|
+
left: auto;
|
|
3347
|
+
right: 0;
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
[dir="rtl"] .eb-seekbar-thumb {
|
|
3351
|
+
right: auto;
|
|
3352
|
+
left: -6px;
|
|
3353
|
+
}
|
|
3354
|
+
|
|
3355
|
+
[dir="rtl"] .eb-chapter-marker {
|
|
3356
|
+
transform: translateX(50%);
|
|
3357
|
+
}
|
|
3358
|
+
|
|
3359
|
+
[dir="rtl"] .eb-epg-segment {
|
|
3360
|
+
border-right: none;
|
|
3361
|
+
border-left: 1px solid rgba(255, 255, 255, 0.3);
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3344
3364
|
/* ============================================================
|
|
3345
3365
|
Responsive: ensure container fills parent
|
|
3346
3366
|
============================================================ */
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.EBPlayer = {}));
|
|
5
5
|
})(this, (function (exports) { 'use strict';
|
|
6
6
|
|
|
7
|
-
var __EB_PLAYER_VERSION__ = "2.
|
|
7
|
+
var __EB_PLAYER_VERSION__ = "2.3.1";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Finite State Machine for player playback state transitions.
|
|
@@ -1525,7 +1525,7 @@
|
|
|
1525
1525
|
return null;
|
|
1526
1526
|
}
|
|
1527
1527
|
// ---- EPG helpers ----
|
|
1528
|
-
renderEpgSegments(programs) {
|
|
1528
|
+
renderEpgSegments(programs, isRtl) {
|
|
1529
1529
|
if (programs.length === 0)
|
|
1530
1530
|
return [];
|
|
1531
1531
|
// Compute the time window from first program start to last program end
|
|
@@ -1536,13 +1536,18 @@
|
|
|
1536
1536
|
return [];
|
|
1537
1537
|
const now = Date.now();
|
|
1538
1538
|
return programs.map((program) => {
|
|
1539
|
-
const
|
|
1539
|
+
const startPercent = ((program.start - windowStart) / windowDuration) * 100;
|
|
1540
1540
|
const widthPercent = ((program.end - program.start) / windowDuration) * 100;
|
|
1541
1541
|
const isCurrent = now >= program.start && now < program.end;
|
|
1542
|
+
// RTL mirrors the timeline: anchor from the track's inline-start edge
|
|
1543
|
+
// (right in RTL) instead of always anchoring from the physical left.
|
|
1544
|
+
const positionStyle = isRtl
|
|
1545
|
+
? `right: ${startPercent.toFixed(2)}%; width: ${widthPercent.toFixed(2)}%`
|
|
1546
|
+
: `left: ${startPercent.toFixed(2)}%; width: ${widthPercent.toFixed(2)}%`;
|
|
1542
1547
|
return b `
|
|
1543
1548
|
<div
|
|
1544
1549
|
class="eb-epg-segment${isCurrent ? ' eb-epg-current' : ''}"
|
|
1545
|
-
style="
|
|
1550
|
+
style="${positionStyle}"
|
|
1546
1551
|
title="${program.title}"
|
|
1547
1552
|
></div>
|
|
1548
1553
|
`;
|
|
@@ -1550,7 +1555,7 @@
|
|
|
1550
1555
|
}
|
|
1551
1556
|
// ---- Template ----
|
|
1552
1557
|
template() {
|
|
1553
|
-
const { currentTime, duration, bufferedEnd, adPlaying, introStreamPlaying, chapters, epgPrograms } = this.state;
|
|
1558
|
+
const { currentTime, duration, bufferedEnd, adPlaying, introStreamPlaying, chapters, epgPrograms, isRtl } = this.state;
|
|
1554
1559
|
const isSeekbarHidden = !this.config.seekbar;
|
|
1555
1560
|
const isDisabled = adPlaying || introStreamPlaying;
|
|
1556
1561
|
// Progress percentage — use dragValue during drag to prevent live-update jitter
|
|
@@ -1563,12 +1568,17 @@
|
|
|
1563
1568
|
const activeChapter = this.findActiveChapter(chapters);
|
|
1564
1569
|
const skippableEntry = activeChapter !== null ? this.findSkippableChapter(activeChapter) : null;
|
|
1565
1570
|
const chapterMarkers = chapters.map((chapter) => {
|
|
1566
|
-
const
|
|
1571
|
+
const positionPercent = duration > 0 ? (chapter.startTime / duration) * 100 : 0;
|
|
1567
1572
|
const isActive = chapter === activeChapter;
|
|
1573
|
+
// RTL mirrors the timeline: anchor from the track's inline-start edge
|
|
1574
|
+
// (right in RTL) instead of always anchoring from the physical left.
|
|
1575
|
+
const positionStyle = isRtl
|
|
1576
|
+
? `right: ${positionPercent.toFixed(2)}%`
|
|
1577
|
+
: `left: ${positionPercent.toFixed(2)}%`;
|
|
1568
1578
|
return b `
|
|
1569
1579
|
<div
|
|
1570
1580
|
class="eb-chapter-marker${isActive ? ' eb-chapter-active' : ''}"
|
|
1571
|
-
style="
|
|
1581
|
+
style="${positionStyle}"
|
|
1572
1582
|
title="${chapter.title}"
|
|
1573
1583
|
></div>
|
|
1574
1584
|
`;
|
|
@@ -1583,7 +1593,7 @@
|
|
|
1583
1593
|
`
|
|
1584
1594
|
: b ``;
|
|
1585
1595
|
// EPG segments
|
|
1586
|
-
const epgSegments = this.renderEpgSegments(epgPrograms);
|
|
1596
|
+
const epgSegments = this.renderEpgSegments(epgPrograms, isRtl);
|
|
1587
1597
|
// Tooltip — for live streams, show wall-clock time at hovered position
|
|
1588
1598
|
const tooltipTimeText = this.state.isLive && Number.isFinite(duration)
|
|
1589
1599
|
? formatWallClock(Date.now() - (duration - this.tooltipTime) * 1000)
|
|
@@ -4388,6 +4398,170 @@
|
|
|
4388
4398
|
};
|
|
4389
4399
|
}
|
|
4390
4400
|
|
|
4401
|
+
var subtitlesParserVtt = {exports: {}};
|
|
4402
|
+
|
|
4403
|
+
var hasRequiredSubtitlesParserVtt;
|
|
4404
|
+
|
|
4405
|
+
function requireSubtitlesParserVtt () {
|
|
4406
|
+
if (hasRequiredSubtitlesParserVtt) return subtitlesParserVtt.exports;
|
|
4407
|
+
hasRequiredSubtitlesParserVtt = 1;
|
|
4408
|
+
(function (module, exports) {
|
|
4409
|
+
var parser = (function () {
|
|
4410
|
+
var pItems = {};
|
|
4411
|
+
|
|
4412
|
+
/**
|
|
4413
|
+
* Converts SubRip subtitles into array of objects
|
|
4414
|
+
* [{
|
|
4415
|
+
* id: `Number of subtitle`
|
|
4416
|
+
* startTime: `Start time of subtitle`
|
|
4417
|
+
* endTime: `End time of subtitle
|
|
4418
|
+
* text: `Text of subtitle`
|
|
4419
|
+
* }]
|
|
4420
|
+
*
|
|
4421
|
+
* @param {String} data SubRip suntitles string
|
|
4422
|
+
* @param {Boolean} ms Optional: use milliseconds for startTime and endTime
|
|
4423
|
+
* @return {Array}
|
|
4424
|
+
*/
|
|
4425
|
+
pItems.fromVtt = function (data, timeFormat, isYoutubeAutoTranscript) {
|
|
4426
|
+
var useYoutubeAutoTranscript = isYoutubeAutoTranscript ? true : false;
|
|
4427
|
+
data = data.replace(/\r/g, "");
|
|
4428
|
+
var regex = /(\d+)?\n?(\d{2}:\d{2}:\d{2}[,.]\d{3}) --> (\d{2}:\d{2}:\d{2}[,.]\d{3}).*\n/g;
|
|
4429
|
+
data = data.split(regex);
|
|
4430
|
+
data.shift();
|
|
4431
|
+
var items = [];
|
|
4432
|
+
for (var i = 0; i < data.length; i += 4) {
|
|
4433
|
+
var text = data[i + 3];
|
|
4434
|
+
if (useYoutubeAutoTranscript) {
|
|
4435
|
+
text = text.split("\n");
|
|
4436
|
+
text.shift();
|
|
4437
|
+
text = text.join("\n");
|
|
4438
|
+
}
|
|
4439
|
+
if (text.trim().length === 0) continue;
|
|
4440
|
+
items.push({
|
|
4441
|
+
id: data[i] ? +data[i].trim() : items.length + 1,
|
|
4442
|
+
startTime: changeTimeFormat(data[i + 1].trim(), timeFormat),
|
|
4443
|
+
endTime: changeTimeFormat(data[i + 2].trim(), timeFormat),
|
|
4444
|
+
text: text.trim(),
|
|
4445
|
+
});
|
|
4446
|
+
}
|
|
4447
|
+
return items;
|
|
4448
|
+
};
|
|
4449
|
+
|
|
4450
|
+
/**
|
|
4451
|
+
* Converts Array of objects created by this module to SubRip subtitles
|
|
4452
|
+
* @param {Array} data
|
|
4453
|
+
* @return {String} SubRip subtitles string
|
|
4454
|
+
*/
|
|
4455
|
+
pItems.toVtt = function (data) {
|
|
4456
|
+
if (!data instanceof Array) return "";
|
|
4457
|
+
var res = "";
|
|
4458
|
+
|
|
4459
|
+
for (var i = 0; i < data.length; i++) {
|
|
4460
|
+
var s = data[i];
|
|
4461
|
+
|
|
4462
|
+
if (!isNaN(s.startTime) && !isNaN(s.endTime)) {
|
|
4463
|
+
s.startTime = msTime(parseInt(s.startTime, 10));
|
|
4464
|
+
s.endTime = msTime(parseInt(s.endTime, 10));
|
|
4465
|
+
}
|
|
4466
|
+
|
|
4467
|
+
res += s.id + "\r\n";
|
|
4468
|
+
res += s.startTime + " --> " + s.endTime + "\r\n";
|
|
4469
|
+
res += s.text.replace("\n", "\r\n") + "\r\n\r\n";
|
|
4470
|
+
}
|
|
4471
|
+
|
|
4472
|
+
return res;
|
|
4473
|
+
};
|
|
4474
|
+
|
|
4475
|
+
var changeTimeFormat = function (time, format) {
|
|
4476
|
+
if (format === "ms") {
|
|
4477
|
+
return timeMs(time);
|
|
4478
|
+
} else if (format === "s") {
|
|
4479
|
+
return timeMs(time) / 1000;
|
|
4480
|
+
} else {
|
|
4481
|
+
return time;
|
|
4482
|
+
}
|
|
4483
|
+
};
|
|
4484
|
+
|
|
4485
|
+
var timeMs = function (val) {
|
|
4486
|
+
var regex = /(\d+):(\d{2}):(\d{2})[,.](\d{3})/;
|
|
4487
|
+
var parts = regex.exec(val);
|
|
4488
|
+
|
|
4489
|
+
if (parts === null) {
|
|
4490
|
+
return 0;
|
|
4491
|
+
}
|
|
4492
|
+
|
|
4493
|
+
for (var i = 1; i < 5; i++) {
|
|
4494
|
+
parts[i] = parseInt(parts[i], 10);
|
|
4495
|
+
if (isNaN(parts[i])) parts[i] = 0;
|
|
4496
|
+
}
|
|
4497
|
+
|
|
4498
|
+
// hours + minutes + seconds + ms
|
|
4499
|
+
return parts[1] * 3600000 + parts[2] * 60000 + parts[3] * 1000 + parts[4];
|
|
4500
|
+
};
|
|
4501
|
+
|
|
4502
|
+
var msTime = function (val) {
|
|
4503
|
+
var measures = [3600000, 60000, 1000];
|
|
4504
|
+
var time = [];
|
|
4505
|
+
|
|
4506
|
+
for (var i in measures) {
|
|
4507
|
+
var res = ((val / measures[i]) >> 0).toString();
|
|
4508
|
+
|
|
4509
|
+
if (res.length < 2) res = "0" + res;
|
|
4510
|
+
val %= measures[i];
|
|
4511
|
+
time.push(res);
|
|
4512
|
+
}
|
|
4513
|
+
|
|
4514
|
+
var ms = val.toString();
|
|
4515
|
+
if (ms.length < 3) {
|
|
4516
|
+
for (i = 0; i <= 3 - ms.length; i++) ms = "0" + ms;
|
|
4517
|
+
}
|
|
4518
|
+
|
|
4519
|
+
return time.join(":") + "," + ms;
|
|
4520
|
+
};
|
|
4521
|
+
|
|
4522
|
+
return pItems;
|
|
4523
|
+
})();
|
|
4524
|
+
|
|
4525
|
+
// ignore exports for browser
|
|
4526
|
+
{
|
|
4527
|
+
module.exports = parser;
|
|
4528
|
+
}
|
|
4529
|
+
} (subtitlesParserVtt));
|
|
4530
|
+
return subtitlesParserVtt.exports;
|
|
4531
|
+
}
|
|
4532
|
+
|
|
4533
|
+
var subtitlesParserVttExports = requireSubtitlesParserVtt();
|
|
4534
|
+
|
|
4535
|
+
/**
|
|
4536
|
+
* Parses a WebVTT string into an array of ChapterMark objects.
|
|
4537
|
+
* Returns an empty array if the content is empty or contains no parseable cues.
|
|
4538
|
+
*/
|
|
4539
|
+
function parseChapters(vttContent) {
|
|
4540
|
+
if (!vttContent || !vttContent.trim()) {
|
|
4541
|
+
return [];
|
|
4542
|
+
}
|
|
4543
|
+
const cues = subtitlesParserVttExports.fromVtt(vttContent, 's');
|
|
4544
|
+
return cues.map((cue) => ({
|
|
4545
|
+
title: cue.text,
|
|
4546
|
+
startTime: cue.startTime,
|
|
4547
|
+
endTime: cue.endTime
|
|
4548
|
+
}));
|
|
4549
|
+
}
|
|
4550
|
+
/**
|
|
4551
|
+
* Fetches a WebVTT chapters file from a URL and parses it.
|
|
4552
|
+
* Returns an empty array if the fetch or parse fails.
|
|
4553
|
+
*/
|
|
4554
|
+
async function fetchChapters(url) {
|
|
4555
|
+
try {
|
|
4556
|
+
const response = await fetch(url);
|
|
4557
|
+
const text = await response.text();
|
|
4558
|
+
return parseChapters(text);
|
|
4559
|
+
}
|
|
4560
|
+
catch {
|
|
4561
|
+
return [];
|
|
4562
|
+
}
|
|
4563
|
+
}
|
|
4564
|
+
|
|
4391
4565
|
/**
|
|
4392
4566
|
* PlayerController manages the complete lifecycle of a player instance.
|
|
4393
4567
|
*
|
|
@@ -4588,6 +4762,25 @@
|
|
|
4588
4762
|
// CRITICAL: AbortController is one-shot. Create a fresh one for re-mount.
|
|
4589
4763
|
this.abortController = new AbortController();
|
|
4590
4764
|
}
|
|
4765
|
+
/**
|
|
4766
|
+
* Populate state.chapters from config.chapters.
|
|
4767
|
+
*
|
|
4768
|
+
* The value is either an inline WebVTT string or an http(s) URL pointing at a
|
|
4769
|
+
* .vtt file. A remote fetch is fire-and-forget and non-critical: fetchChapters
|
|
4770
|
+
* resolves to an empty array on failure, and a result arriving after dispose()
|
|
4771
|
+
* is discarded rather than written into a torn-down state.
|
|
4772
|
+
*/
|
|
4773
|
+
loadChapters(source, state, signal) {
|
|
4774
|
+
if (/^https?:\/\//i.test(source.trim())) {
|
|
4775
|
+
void fetchChapters(source).then((chapters) => {
|
|
4776
|
+
if (!signal.aborted) {
|
|
4777
|
+
state.chapters = chapters;
|
|
4778
|
+
}
|
|
4779
|
+
});
|
|
4780
|
+
return;
|
|
4781
|
+
}
|
|
4782
|
+
state.chapters = parseChapters(source);
|
|
4783
|
+
}
|
|
4591
4784
|
/**
|
|
4592
4785
|
* Initialize integration managers based on config flags.
|
|
4593
4786
|
* All integrations are async and non-critical — errors are caught and logged.
|
|
@@ -4630,6 +4823,10 @@
|
|
|
4630
4823
|
});
|
|
4631
4824
|
}
|
|
4632
4825
|
}
|
|
4826
|
+
// Chapters: skip when config.chapters is not set
|
|
4827
|
+
if (config.chapters) {
|
|
4828
|
+
this.loadChapters(config.chapters, state, signal);
|
|
4829
|
+
}
|
|
4633
4830
|
// IntroStreamManager: skip when config.preroll is false (D-02)
|
|
4634
4831
|
if (config.preroll && this.skinRoot !== null) {
|
|
4635
4832
|
const introManager = new IntroStreamManager();
|