@cinecrew/cinecrew-player 0.1.1 → 0.1.2
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/README.md +20 -16
- package/package.json +1 -2
- package/src/data/README.md +5 -0
- package/src/data/emoji-data.json +1 -0
- package/src/data/emoji.js +30 -0
- package/src/native/MediaPlayerView.js +28 -12
- package/src/native/media/EmojiPickerModal.js +217 -21
- package/src/native/media/LiveChatDrawer.js +261 -103
- package/src/native/media/YouTubeVideoPlayer.web.js +64 -10
- package/src/native/media/player/PlayerBottomBar.js +4 -2
- package/src/native/media/player/PlayerTopBar.js +2 -4
- package/src/web/index.js +386 -99
- package/src/web/styles.css +341 -17
- package/types/index.d.ts +22 -7
package/src/web/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
|
1
|
+
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { useWebVideoAspectRatio } from '../native/media/web/useWebVideoAspectRatio';
|
|
3
3
|
import { useWebMpegTsPlayback } from '../native/media/web/useWebMpegTsPlayback.web';
|
|
4
4
|
import { useWebHlsPlayback } from '../native/media/web/useWebHlsPlayback.web';
|
|
5
5
|
import { useWebAc3AudioPlayback } from '../native/media/web/useWebAc3AudioPlayback.web';
|
|
6
6
|
import { YouTubeVideoPlayer } from '../native/media/YouTubeVideoPlayer.web.js';
|
|
7
7
|
import { getYouTubeVideoId, getWebRuntimePlatform, useResolvedPlayerSource } from '../utils/sourceUtils';
|
|
8
|
+
import { EMOJI_GROUPS, searchEmojis } from '../data/emoji';
|
|
8
9
|
import './styles.css';
|
|
9
10
|
|
|
10
11
|
const h = React.createElement;
|
|
@@ -20,8 +21,9 @@ const DEFAULT_THEME = {
|
|
|
20
21
|
const DEFAULT_ICONS = {
|
|
21
22
|
play: 'play', pause: 'pause', restart: 'restart', lock: 'lock', unlock: 'lock-open',
|
|
22
23
|
mute: 'volume-mute', unmute: 'volume-high', aspectRatio: 'aspect-ratio', videoOnly: 'video',
|
|
23
|
-
audio: 'music-note',
|
|
24
|
-
liveChat: 'comment-text-multiple-outline', epg: 'television-classic',
|
|
24
|
+
audio: 'music-note', back: 'arrow-left', recording: 'record-rec', stop: 'stop',
|
|
25
|
+
liveChat: 'comment-text-multiple-outline', epg: 'television-classic', diagnostics: 'logs',
|
|
26
|
+
fullscreen: 'fullscreen', close: 'close',
|
|
25
27
|
};
|
|
26
28
|
|
|
27
29
|
// Inline SVG keeps the React DOM/Electron entry independent of React Native,
|
|
@@ -38,6 +40,7 @@ const WEB_ICON_PATHS = {
|
|
|
38
40
|
video: 'M18 7V5a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2l4 4V5zm-2 12H4V5h12z',
|
|
39
41
|
'music-note': 'M12 3v12.26A4 4 0 1 0 14 19V7h6V3z',
|
|
40
42
|
'arrow-collapse': 'M4 4h6v2H6v4H4zm10 0h6v6h-2V6h-4zM4 14h2v4h4v2H4zm14 0h2v6h-6v-2h4z',
|
|
43
|
+
logs: 'M3 3h18v18H3zm2 2v14h14V5zm3 3h2v2H8zm4 0h7v2h-7zm-4 4h2v2H8zm4 0h7v2h-7zm-4 4h2v2H8zm4 0h7v2h-7z',
|
|
41
44
|
'arrow-left': 'M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20z',
|
|
42
45
|
'record-rec': 'M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zm0 15a5 5 0 1 1 0-10 5 5 0 0 1 0 10z',
|
|
43
46
|
stop: 'M6 6h12v12H6z',
|
|
@@ -114,14 +117,43 @@ function getPanelActionName(panel) {
|
|
|
114
117
|
}
|
|
115
118
|
|
|
116
119
|
function WebSeekControl({ currentTime, duration, theme, onSeek }) {
|
|
120
|
+
const [seekDraft, setSeekDraft] = useState(currentTime);
|
|
121
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
122
|
+
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
if (!isDragging) setSeekDraft(currentTime);
|
|
125
|
+
}, [currentTime, isDragging]);
|
|
126
|
+
|
|
127
|
+
const updateSeek = (event) => {
|
|
128
|
+
const nextTime = Number(event.currentTarget.value);
|
|
129
|
+
setSeekDraft(nextTime);
|
|
130
|
+
onSeek(nextTime);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const finishSeek = (event) => {
|
|
134
|
+
if (!isDragging) return;
|
|
135
|
+
updateSeek(event);
|
|
136
|
+
setIsDragging(false);
|
|
137
|
+
};
|
|
138
|
+
|
|
117
139
|
return h('div', { className: 'cinecrew-player__seek' },
|
|
118
140
|
h('span', null, formatTime(currentTime)),
|
|
119
141
|
h('input', {
|
|
120
142
|
type: 'range',
|
|
143
|
+
'aria-label': 'Seek video',
|
|
121
144
|
min: 0,
|
|
122
145
|
max: duration,
|
|
123
|
-
|
|
124
|
-
|
|
146
|
+
step: 'any',
|
|
147
|
+
value: Math.min(isDragging ? seekDraft : currentTime, duration),
|
|
148
|
+
onChange: updateSeek,
|
|
149
|
+
onPointerDown: (event) => {
|
|
150
|
+
event.stopPropagation();
|
|
151
|
+
setIsDragging(true);
|
|
152
|
+
setSeekDraft(Number(event.currentTarget.value));
|
|
153
|
+
},
|
|
154
|
+
onPointerUp: finishSeek,
|
|
155
|
+
onPointerCancel: finishSeek,
|
|
156
|
+
onBlur: finishSeek,
|
|
125
157
|
style: { accentColor: theme.accentColor },
|
|
126
158
|
}),
|
|
127
159
|
h('span', null, formatTime(duration)));
|
|
@@ -138,7 +170,7 @@ function WebAspectRatioMenu({ open, theme, icons, onToggle, onSelect }) {
|
|
|
138
170
|
onClick: () => onSelect(ratio),
|
|
139
171
|
}, ratio)));
|
|
140
172
|
}
|
|
141
|
-
return h('div', { className: 'cinecrew-player__menu-wrap', key: 'aspectRatio' },
|
|
173
|
+
return h('div', { className: 'cinecrew-player__menu-wrap cinecrew-player__menu-wrap--aspect', key: 'aspectRatio' },
|
|
142
174
|
h(PlayerButton, { name: 'aspectRatio', label: 'Aspect ratio', icons, theme, onClick: onToggle, active: open }),
|
|
143
175
|
menu);
|
|
144
176
|
}
|
|
@@ -170,8 +202,8 @@ function WebPlaybackRateControl({ value, onChange }) {
|
|
|
170
202
|
|
|
171
203
|
function WebBottomControls(props) {
|
|
172
204
|
const {
|
|
173
|
-
isLive, overrides, theme, icons, currentTime, duration, seekTo,
|
|
174
|
-
|
|
205
|
+
isLive, overrides, theme, icons, currentTime, duration, seekTo,
|
|
206
|
+
showAspectMenu, setShowAspectMenu, selectAspect, videoOnly,
|
|
175
207
|
setVideoOnlyMode, audioOnly, setAudioOnlyMode, availableTracks,
|
|
176
208
|
showAudioMenu, setShowAudioMenu, selectAudio, playbackRate,
|
|
177
209
|
setPlaybackRateAction, fullscreen, toggleFullscreen,
|
|
@@ -211,27 +243,28 @@ function WebBottomControls(props) {
|
|
|
211
243
|
if (isControlEnabled(overrides, 'playbackRate', true) && !isLive) {
|
|
212
244
|
playbackRateControl = h(WebPlaybackRateControl, { value: playbackRate, onChange: setPlaybackRateAction });
|
|
213
245
|
}
|
|
214
|
-
const muteLabel = muted ? 'Unmute' : 'Mute';
|
|
215
|
-
const muteIcon = muted ? 'mute' : 'unmute';
|
|
216
246
|
const videoOnlyLabel = videoOnly ? 'Enable audio' : 'Video only';
|
|
217
247
|
const audioOnlyLabel = audioOnly ? 'Switch to audio card' : 'Audio only';
|
|
218
248
|
const fullscreenLabel = fullscreen ? 'Exit full screen' : 'Full screen';
|
|
219
249
|
|
|
220
250
|
return h('div', { className: 'cinecrew-player__bottom-controls' },
|
|
221
251
|
seek,
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
252
|
+
h('div', { className: 'cinecrew-player__bottom-actions' },
|
|
253
|
+
h('div', { className: 'cinecrew-player__bottom-left-actions' },
|
|
254
|
+
renderControlButton({ name: 'videoOnly', label: videoOnlyLabel, callback: () => setVideoOnlyMode(!videoOnly), options: { active: videoOnly, defaultVisible: false }, overrides, icons, theme }),
|
|
255
|
+
aspect),
|
|
256
|
+
h('div', { className: 'cinecrew-player__bottom-right-actions' },
|
|
257
|
+
renderControlButton({ name: 'audioOnly', label: audioOnlyLabel, callback: () => setAudioOnlyMode(true), options: { icon: 'audio', active: audioOnly }, overrides, icons, theme }),
|
|
258
|
+
audioTracks,
|
|
259
|
+
playbackRateControl,
|
|
260
|
+
renderControlButton({ name: 'fullscreen', label: fullscreenLabel, callback: toggleFullscreen, options: { icon: 'fullscreen' }, overrides, icons, theme }))));
|
|
229
261
|
}
|
|
230
262
|
|
|
231
|
-
function WebPlayerControls({ locked, overrides, theme, icons, unlockedControls, toggleLock, paused, togglePlay, bottomProps }) {
|
|
232
|
-
let
|
|
263
|
+
function WebPlayerControls({ locked, buffering, overrides, theme, icons, unlockedControls, toggleLock, paused, togglePlay, bottomProps }) {
|
|
264
|
+
let leftControls = unlockedControls.left;
|
|
265
|
+
let rightControls = unlockedControls.right;
|
|
233
266
|
if (locked) {
|
|
234
|
-
|
|
267
|
+
rightControls = renderControlButton({
|
|
235
268
|
name: 'lock', label: 'Unlock controls', callback: toggleLock,
|
|
236
269
|
options: { active: true, defaultVisible: true }, overrides, icons, theme,
|
|
237
270
|
});
|
|
@@ -239,14 +272,18 @@ function WebPlayerControls({ locked, overrides, theme, icons, unlockedControls,
|
|
|
239
272
|
let centerControls = null;
|
|
240
273
|
let bottomControls = null;
|
|
241
274
|
if (!locked) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
275
|
+
if (!buffering) {
|
|
276
|
+
const playLabel = paused ? 'Play' : 'Pause';
|
|
277
|
+
const playIcon = paused ? 'play' : 'pause';
|
|
278
|
+
centerControls = h('div', { className: 'cinecrew-player__center-controls' },
|
|
279
|
+
renderControlButton({ name: 'playPause', label: playLabel, callback: togglePlay, options: { icon: playIcon }, overrides, icons, theme }));
|
|
280
|
+
}
|
|
246
281
|
bottomControls = h(WebBottomControls, bottomProps);
|
|
247
282
|
}
|
|
248
283
|
return h('div', { className: 'cinecrew-player__controls', style: { color: theme.controlColor } },
|
|
249
|
-
h('div', { className: 'cinecrew-player__top-controls' },
|
|
284
|
+
h('div', { className: 'cinecrew-player__top-controls' },
|
|
285
|
+
h('div', { className: 'cinecrew-player__top-left-actions' }, leftControls),
|
|
286
|
+
h('div', { className: 'cinecrew-player__top-right-actions' }, rightControls)),
|
|
250
287
|
centerControls,
|
|
251
288
|
bottomControls);
|
|
252
289
|
}
|
|
@@ -265,6 +302,7 @@ function WebPlayerSurface({
|
|
|
265
302
|
playbackRate,
|
|
266
303
|
videoOnly,
|
|
267
304
|
videoStyle,
|
|
305
|
+
drawerResize,
|
|
268
306
|
audioOnly,
|
|
269
307
|
inlinePreview,
|
|
270
308
|
onPromotePreview,
|
|
@@ -292,6 +330,7 @@ function WebPlayerSurface({
|
|
|
292
330
|
onStateChange,
|
|
293
331
|
onError,
|
|
294
332
|
onEnded,
|
|
333
|
+
style: drawerResize ? { width: '64%', height: '64%', inset: '18% auto auto 0' } : undefined,
|
|
295
334
|
});
|
|
296
335
|
}
|
|
297
336
|
if (!streamUrl) return h('div', { className: 'cinecrew-player__empty' });
|
|
@@ -304,9 +343,14 @@ function WebPlayerSurface({
|
|
|
304
343
|
muted: muted || videoOnly,
|
|
305
344
|
playsInline: true,
|
|
306
345
|
preload: 'auto',
|
|
307
|
-
style: {
|
|
346
|
+
style: {
|
|
347
|
+
...videoStyle,
|
|
348
|
+
...(drawerResize ? { width: '64%', height: '64%', top: '18%', right: 'auto', bottom: 'auto' } : {}),
|
|
349
|
+
opacity: audioOnly ? 0 : 1,
|
|
350
|
+
},
|
|
308
351
|
onClick: inlinePreview ? onPromotePreview : undefined,
|
|
309
352
|
onError: (event) => {
|
|
353
|
+
if (!directVideoSource) return;
|
|
310
354
|
const mediaError = event.currentTarget?.error;
|
|
311
355
|
handleError({ message: mediaError?.message || 'The browser could not load this stream. Check URL, codec and CORS support.', code: mediaError?.code, cause: mediaError });
|
|
312
356
|
},
|
|
@@ -346,7 +390,9 @@ function isAudioPlaybackEnabled(muted, videoOnly) {
|
|
|
346
390
|
}
|
|
347
391
|
|
|
348
392
|
function getPanelIntegration(activePanel, integrations) {
|
|
349
|
-
|
|
393
|
+
if (activePanel === 'chat') return integrations.liveChat;
|
|
394
|
+
if (activePanel === 'epg') return integrations.epg;
|
|
395
|
+
return null;
|
|
350
396
|
}
|
|
351
397
|
|
|
352
398
|
function getActiveMediaUrl(youtubeVideoId, streamUrl) {
|
|
@@ -359,15 +405,11 @@ function getInlinePlayerStyle(inlinePreview, rect) {
|
|
|
359
405
|
}
|
|
360
406
|
|
|
361
407
|
function buildUnlockedControls({
|
|
362
|
-
control,
|
|
408
|
+
control, onBack, isLive, restart, toggleLock, toggleMute, muted, hasRecording,
|
|
363
409
|
recording, integrations, action, videoRef, streamUrl, title, activePanel,
|
|
364
|
-
hasChat, hasEpg,
|
|
410
|
+
hasChat, hasEpg, hasDiagnostics, handleOpenPanel, setRecording, backVisible,
|
|
365
411
|
}) {
|
|
366
|
-
const
|
|
367
|
-
control('back', 'Back', onBack, { defaultVisible: backVisible }),
|
|
368
|
-
...(!isLive ? [control('restart', 'Restart', restart)] : []),
|
|
369
|
-
control('lock', 'Lock controls', toggleLock, { active: false, defaultVisible: true }),
|
|
370
|
-
];
|
|
412
|
+
const leftControls = [control('back', 'Back', onBack, { defaultVisible: backVisible })];
|
|
371
413
|
let recordingControl = null;
|
|
372
414
|
if (hasRecording) {
|
|
373
415
|
const recordingLabel = recording ? 'Stop recording' : 'Start recording';
|
|
@@ -382,19 +424,39 @@ function buildUnlockedControls({
|
|
|
382
424
|
}
|
|
383
425
|
const chatLabel = activePanel === 'chat' ? 'Close live chat' : 'Live chat';
|
|
384
426
|
const epgLabel = activePanel === 'epg' ? 'Close programme guide' : 'Programme guide';
|
|
385
|
-
|
|
386
|
-
...controls,
|
|
427
|
+
const rightControls = [
|
|
387
428
|
recordingControl,
|
|
388
429
|
control('liveChat', chatLabel, () => handleOpenPanel('chat'), { active: activePanel === 'chat', defaultVisible: hasChat }),
|
|
389
430
|
control('epg', epgLabel, () => handleOpenPanel('epg'), { active: activePanel === 'epg', defaultVisible: hasEpg }),
|
|
390
|
-
control('
|
|
431
|
+
...(!isLive ? [control('restart', 'Restart', restart)] : []),
|
|
391
432
|
];
|
|
433
|
+
const muteLabel = muted ? 'Unmute' : 'Mute';
|
|
434
|
+
rightControls.push(
|
|
435
|
+
control('mute', muteLabel, toggleMute, { icon: muted ? 'mute' : 'unmute' }),
|
|
436
|
+
control('diagnostics', 'Stream diagnostics', () => handleOpenPanel('diagnostics'), { active: activePanel === 'diagnostics', defaultVisible: hasDiagnostics }),
|
|
437
|
+
control('lock', 'Lock controls', toggleLock, { active: false, defaultVisible: true }),
|
|
438
|
+
);
|
|
439
|
+
return { left: leftControls, right: rightControls };
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function getDrawerLabel(activePanel) {
|
|
443
|
+
if (activePanel === 'chat') return 'Live chat drawer';
|
|
444
|
+
if (activePanel === 'epg') return 'Programme guide drawer';
|
|
445
|
+
return 'Stream diagnostics drawer';
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function getPlaybackStatus(error, buffering, paused) {
|
|
449
|
+
if (error) return 'Error';
|
|
450
|
+
if (buffering) return 'Buffering';
|
|
451
|
+
return paused ? 'Paused' : 'Playing';
|
|
392
452
|
}
|
|
393
453
|
|
|
394
454
|
function WebPlayerLayout(props) {
|
|
395
|
-
|
|
396
|
-
|
|
455
|
+
let controlLayer = null;
|
|
456
|
+
if (!props.error && !props.audioOnly) {
|
|
457
|
+
controlLayer = h(WebPlayerControls, {
|
|
397
458
|
locked: props.locked,
|
|
459
|
+
buffering: props.buffering,
|
|
398
460
|
overrides: props.controlOverrides,
|
|
399
461
|
theme: props.theme,
|
|
400
462
|
icons: props.icons,
|
|
@@ -403,24 +465,32 @@ function WebPlayerLayout(props) {
|
|
|
403
465
|
paused: props.isPaused,
|
|
404
466
|
togglePlay: props.togglePlay,
|
|
405
467
|
bottomProps: props.bottomControlProps,
|
|
406
|
-
})
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
:
|
|
411
|
-
|
|
412
|
-
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
let playerTitle = null;
|
|
471
|
+
if (props.title && !props.audioOnly) {
|
|
472
|
+
playerTitle = h('div', { className: 'cinecrew-player__title', style: { color: props.theme.controlColor } }, props.title);
|
|
473
|
+
}
|
|
474
|
+
let loadingNotice = null;
|
|
475
|
+
if (props.buffering && !props.error) {
|
|
476
|
+
loadingNotice = h('div', { className: 'cinecrew-player__status', style: { color: props.theme.controlColor } },
|
|
413
477
|
h('span', { className: 'cinecrew-player__spinner', style: { borderTopColor: props.theme.accentColor } }), 'Loading stream…')
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
:
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
478
|
+
}
|
|
479
|
+
let audioCard = null;
|
|
480
|
+
if (props.audioOnly) {
|
|
481
|
+
audioCard = h(WebAudioOnlyCard, { poster: props.poster, title: props.title, theme: props.theme, onSwitchToVideo: props.onSwitchToVideo });
|
|
482
|
+
}
|
|
483
|
+
let panelNode = null;
|
|
484
|
+
if (props.activePanel && props.webPanel) {
|
|
485
|
+
panelNode = h('aside', {
|
|
486
|
+
className: `cinecrew-player__panel${props.drawerMode === 'resize' ? ' is-resizing' : ''}`,
|
|
487
|
+
style: { color: props.theme.controlColor, ...props.drawerStyle },
|
|
488
|
+
'aria-label': getDrawerLabel(props.activePanel),
|
|
489
|
+
}, props.webPanel);
|
|
490
|
+
}
|
|
421
491
|
return h('div', {
|
|
422
492
|
ref: props.playerRef,
|
|
423
|
-
className: `cinecrew-player${props.inlinePreview ? ' cinecrew-player--inline-preview' : ''} ${props.className}`.trim(),
|
|
493
|
+
className: `cinecrew-player${props.inlinePreview ? ' cinecrew-player--inline-preview' : ''}${props.drawerMode === 'resize' && props.activePanel ? ' cinecrew-player--drawer-resize' : ''} ${props.className}`.trim(),
|
|
424
494
|
style: { ...props.rootStyle, ...props.style, background: props.theme.backgroundColor, borderRadius: props.theme.borderRadius, '--cinecrew-accent': props.theme.accentColor, '--cinecrew-text': props.theme.controlColor, '--cinecrew-surface': props.theme.surfaceColor },
|
|
425
495
|
onWheel: props.onWheel,
|
|
426
496
|
'data-stream-mode': props.streamMode,
|
|
@@ -473,7 +543,13 @@ function getPanelRenderer(activePanel, renderLiveChat, renderEpg, integrations)
|
|
|
473
543
|
return null;
|
|
474
544
|
}
|
|
475
545
|
|
|
476
|
-
function createWebPanelNode({
|
|
546
|
+
function createWebPanelNode({
|
|
547
|
+
renderer, integration, activePanel, integrations, source, title, theme, onClose,
|
|
548
|
+
diagnostics, messagePageSize,
|
|
549
|
+
}) {
|
|
550
|
+
if (activePanel === 'diagnostics') {
|
|
551
|
+
return h(WebDiagnosticsPanel, { title, theme, ...diagnostics });
|
|
552
|
+
}
|
|
477
553
|
if (renderer) return h(renderer, { title, source, onClose });
|
|
478
554
|
if (!integration) return null;
|
|
479
555
|
const hasLoader = activePanel === 'chat'
|
|
@@ -488,20 +564,195 @@ function createWebPanelNode({ renderer, integration, activePanel, integrations,
|
|
|
488
564
|
title,
|
|
489
565
|
theme,
|
|
490
566
|
onClose,
|
|
567
|
+
messagePageSize,
|
|
491
568
|
});
|
|
492
569
|
}
|
|
493
570
|
|
|
494
|
-
function
|
|
571
|
+
function WebDiagnosticsPanel({ title, theme, streamMode, status, currentTime, duration, videoRef }) {
|
|
572
|
+
const video = videoRef?.current;
|
|
573
|
+
const resolution = video?.videoWidth && video?.videoHeight
|
|
574
|
+
? `${video.videoWidth} × ${video.videoHeight}`
|
|
575
|
+
: 'Not available';
|
|
576
|
+
const rows = [
|
|
577
|
+
['Title', title || 'Untitled'],
|
|
578
|
+
['Stream type', streamMode || 'Unknown'],
|
|
579
|
+
['Playback', status || 'Unknown'],
|
|
580
|
+
['Resolution', resolution],
|
|
581
|
+
['Position', `${formatTime(currentTime)} / ${formatTime(duration)}`],
|
|
582
|
+
];
|
|
583
|
+
return h('section', {
|
|
584
|
+
className: 'cinecrew-player__diagnostics',
|
|
585
|
+
style: { color: theme.controlColor },
|
|
586
|
+
'aria-label': 'Stream diagnostics',
|
|
587
|
+
},
|
|
588
|
+
h('strong', null, 'Stream diagnostics'),
|
|
589
|
+
rows.map(([label, value]) => h('div', { className: 'cinecrew-player__diagnostic-row', key: label },
|
|
590
|
+
h('span', null, label), h('span', null, value))));
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function getChatMessageKey(message, index = 0) {
|
|
594
|
+
return String(message?.id ?? message?.messageId ?? message?.createdAt ?? message?.timestamp
|
|
595
|
+
?? `${message?.username || message?.userName || ''}:${message?.comment || message?.message || message?.text || index}`);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function mergeChatMessages(existing, incoming, prepend = false) {
|
|
599
|
+
const messages = prepend ? [...incoming, ...existing] : [...existing, ...incoming];
|
|
600
|
+
const unique = new Map();
|
|
601
|
+
messages.forEach((message, index) => unique.set(getChatMessageKey(message, index), message));
|
|
602
|
+
return [...unique.values()];
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function formatChatTimestamp(value) {
|
|
606
|
+
if (value === null || value === undefined || value === '') return '';
|
|
607
|
+
const date = new Date(value);
|
|
608
|
+
return Number.isNaN(date.getTime())
|
|
609
|
+
? String(value)
|
|
610
|
+
: date.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const EMOJI_BATCH_SIZE = 96;
|
|
614
|
+
|
|
615
|
+
function WebEmojiPicker({ onSelect }) {
|
|
616
|
+
const [emojiGroup, setEmojiGroup] = useState(EMOJI_GROUPS[0].name);
|
|
617
|
+
const [emojiQuery, setEmojiQuery] = useState('');
|
|
618
|
+
const [visibleCount, setVisibleCount] = useState(EMOJI_BATCH_SIZE);
|
|
619
|
+
const listRef = useRef(null);
|
|
620
|
+
const activeGroup = EMOJI_GROUPS.find((group) => group.name === emojiGroup) || EMOJI_GROUPS[0];
|
|
621
|
+
const filteredEmojis = useMemo(
|
|
622
|
+
() => emojiQuery.trim() ? searchEmojis(emojiQuery) : activeGroup.items,
|
|
623
|
+
[activeGroup, emojiQuery],
|
|
624
|
+
);
|
|
625
|
+
const visibleEmojis = filteredEmojis.slice(0, visibleCount);
|
|
626
|
+
|
|
627
|
+
useEffect(() => {
|
|
628
|
+
setVisibleCount(EMOJI_BATCH_SIZE);
|
|
629
|
+
if (listRef.current) listRef.current.scrollTop = 0;
|
|
630
|
+
}, [emojiGroup, emojiQuery]);
|
|
631
|
+
|
|
632
|
+
const loadMore = () => setVisibleCount((count) => Math.min(count + EMOJI_BATCH_SIZE, filteredEmojis.length));
|
|
633
|
+
const handleScroll = (event) => {
|
|
634
|
+
const element = event.currentTarget;
|
|
635
|
+
if (element.scrollTop + element.clientHeight >= element.scrollHeight - 32) loadMore();
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
return h('div', { className: 'cinecrew-player__emoji-picker', role: 'dialog', 'aria-label': 'Choose an emoji' },
|
|
639
|
+
h('input', {
|
|
640
|
+
className: 'cinecrew-player__emoji-search',
|
|
641
|
+
type: 'search',
|
|
642
|
+
value: emojiQuery,
|
|
643
|
+
onChange: (event) => setEmojiQuery(event.target.value),
|
|
644
|
+
placeholder: 'Search all emojis',
|
|
645
|
+
'aria-label': 'Search all emojis',
|
|
646
|
+
}),
|
|
647
|
+
h('nav', { className: 'cinecrew-player__emoji-categories', 'aria-label': 'Emoji categories' },
|
|
648
|
+
EMOJI_GROUPS.map((group) => h('button', {
|
|
649
|
+
key: group.name,
|
|
650
|
+
type: 'button',
|
|
651
|
+
className: !emojiQuery.trim() && group.name === emojiGroup ? 'is-active' : '',
|
|
652
|
+
onClick: () => {
|
|
653
|
+
setEmojiGroup(group.name);
|
|
654
|
+
setEmojiQuery('');
|
|
655
|
+
},
|
|
656
|
+
'aria-label': group.name,
|
|
657
|
+
title: group.name,
|
|
658
|
+
'aria-pressed': !emojiQuery.trim() && group.name === emojiGroup,
|
|
659
|
+
}, group.icon))),
|
|
660
|
+
h('div', {
|
|
661
|
+
className: 'cinecrew-player__emoji-grid',
|
|
662
|
+
role: 'grid',
|
|
663
|
+
ref: listRef,
|
|
664
|
+
onScroll: handleScroll,
|
|
665
|
+
},
|
|
666
|
+
visibleEmojis.map((item) => h('button', {
|
|
667
|
+
key: item.codepoints,
|
|
668
|
+
type: 'button',
|
|
669
|
+
onClick: () => onSelect(item.emoji),
|
|
670
|
+
'aria-label': `Insert ${item.short_name}`,
|
|
671
|
+
title: item.short_name,
|
|
672
|
+
}, item.emoji)),
|
|
673
|
+
visibleCount < filteredEmojis.length ? h('button', {
|
|
674
|
+
type: 'button',
|
|
675
|
+
className: 'cinecrew-player__emoji-load-more',
|
|
676
|
+
onClick: loadMore,
|
|
677
|
+
}, 'Load more emojis') : null));
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function WebChatComposer({ sending, canSend, onSend }) {
|
|
681
|
+
const [message, setMessage] = useState('');
|
|
682
|
+
const [emojiOpen, setEmojiOpen] = useState(false);
|
|
683
|
+
const handleSubmit = async (event) => {
|
|
684
|
+
event.preventDefault();
|
|
685
|
+
const comment = message.trim();
|
|
686
|
+
if (!comment || sending || !canSend) return;
|
|
687
|
+
const sent = await onSend(comment);
|
|
688
|
+
if (sent) setMessage('');
|
|
689
|
+
};
|
|
690
|
+
const insertEmoji = (emoji) => setMessage((current) => `${current}${emoji}`);
|
|
691
|
+
|
|
692
|
+
return h('form', { className: 'cinecrew-player__chat-form', onSubmit: handleSubmit },
|
|
693
|
+
h('div', { className: 'cinecrew-player__chat-composer' },
|
|
694
|
+
h('button', {
|
|
695
|
+
type: 'button',
|
|
696
|
+
className: 'cinecrew-player__emoji-toggle',
|
|
697
|
+
onClick: () => setEmojiOpen((open) => !open),
|
|
698
|
+
'aria-label': emojiOpen ? 'Close emoji picker' : 'Open emoji picker',
|
|
699
|
+
}, '☺'),
|
|
700
|
+
h('input', {
|
|
701
|
+
value: message,
|
|
702
|
+
onChange: (event) => setMessage(event.target.value),
|
|
703
|
+
placeholder: 'Add a message…',
|
|
704
|
+
'aria-label': 'Chat message',
|
|
705
|
+
maxLength: 1000,
|
|
706
|
+
}),
|
|
707
|
+
h('button', { type: 'submit', disabled: sending || !message.trim() || !canSend }, sending ? 'Sending…' : 'Send')),
|
|
708
|
+
emojiOpen ? h(WebEmojiPicker, { onSelect: insertEmoji }) : null);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function WebPanelRow({ row, index, isChat }) {
|
|
712
|
+
if (isChat) {
|
|
713
|
+
const timestamp = row.createdAt ?? row.timestamp ?? row.sentAt ?? row.time;
|
|
714
|
+
return h('article', { key: row.id ?? row.messageId ?? index, className: 'cinecrew-player__chat-message' },
|
|
715
|
+
h('header', null,
|
|
716
|
+
h('strong', null, row.username || row.userName || row.name || 'Viewer'),
|
|
717
|
+
timestamp ? h('time', { dateTime: String(timestamp) }, formatChatTimestamp(timestamp)) : null),
|
|
718
|
+
h('span', null, row.comment || row.message || row.text || ''));
|
|
719
|
+
}
|
|
720
|
+
const start = row.startMs ?? row.start ?? row.startTime;
|
|
721
|
+
const end = row.endMs ?? row.end ?? row.endTime;
|
|
722
|
+
return h('article', { key: row.id ?? row.startMs ?? index, className: 'cinecrew-player__epg-item' },
|
|
723
|
+
h('small', null, [formatListingTime(start), formatListingTime(end)].filter(Boolean).join(' – ')),
|
|
724
|
+
h('strong', null, row.title || row.name || 'Programme'),
|
|
725
|
+
row.description ? h('span', null, row.description) : null);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function WebLoadMoreMessages({ loading, onClick }) {
|
|
729
|
+
return h('button', {
|
|
730
|
+
type: 'button',
|
|
731
|
+
className: 'cinecrew-player__load-more',
|
|
732
|
+
onClick,
|
|
733
|
+
disabled: loading,
|
|
734
|
+
}, loading ? 'Loading more…' : 'See more messages');
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function WebIntegrationPanel({ kind, integration, integrations, source, title, theme, onClose, messagePageSize = 50 }) {
|
|
495
738
|
const [rows, setRows] = useState([]);
|
|
496
739
|
const [user, setUser] = useState(integrations.user || null);
|
|
497
|
-
const [message, setMessage] = useState('');
|
|
498
740
|
const [loading, setLoading] = useState(true);
|
|
741
|
+
const [loadingOlder, setLoadingOlder] = useState(false);
|
|
742
|
+
const [hasMoreMessages, setHasMoreMessages] = useState(false);
|
|
743
|
+
const [messageOffset, setMessageOffset] = useState(0);
|
|
499
744
|
const [sending, setSending] = useState(false);
|
|
500
745
|
const [error, setError] = useState('');
|
|
746
|
+
const rowsRef = useRef([]);
|
|
501
747
|
const channelId = String(source.streamId || source.mediaId || source.id || '');
|
|
502
748
|
const isChat = kind === 'chat';
|
|
749
|
+
const pageSize = Math.max(1, Math.floor(Number(messagePageSize) || 50));
|
|
503
750
|
const emptyStateMessage = isChat ? 'No messages yet.' : 'No programme information available.';
|
|
504
|
-
|
|
751
|
+
|
|
752
|
+
const updateRows = (nextRows) => {
|
|
753
|
+
rowsRef.current = nextRows;
|
|
754
|
+
setRows(nextRows);
|
|
755
|
+
};
|
|
505
756
|
|
|
506
757
|
const load = useCallback(async () => {
|
|
507
758
|
if (!channelId) {
|
|
@@ -512,15 +763,30 @@ function WebIntegrationPanel({ kind, integration, integrations, source, title, t
|
|
|
512
763
|
try {
|
|
513
764
|
setError('');
|
|
514
765
|
const value = isChat
|
|
515
|
-
? await integration.loadMessages({ channelId, limit:
|
|
766
|
+
? await integration.loadMessages({ channelId, limit: pageSize, offset: 0 })
|
|
516
767
|
: await integration.loadListings({ channelId, limit: integration.limit || 48 });
|
|
517
|
-
|
|
768
|
+
const nextRows = responseRows(value, isChat ? ['messages', 'items', 'comments'] : ['listings', 'programmes', 'epg', 'items']);
|
|
769
|
+
if (isChat) {
|
|
770
|
+
const previousCount = rowsRef.current.length;
|
|
771
|
+
const mergedRows = mergeChatMessages(rowsRef.current, nextRows);
|
|
772
|
+
updateRows(mergedRows);
|
|
773
|
+
const addedCount = Math.max(0, mergedRows.length - previousCount);
|
|
774
|
+
setMessageOffset((offset) => previousCount === 0
|
|
775
|
+
? nextRows.length
|
|
776
|
+
: Math.max(offset, nextRows.length) + addedCount);
|
|
777
|
+
if (previousCount === 0) {
|
|
778
|
+
const explicitHasMore = value?.hasMore ?? value?.pagination?.hasMore;
|
|
779
|
+
setHasMoreMessages(explicitHasMore === undefined ? nextRows.length >= pageSize : Boolean(explicitHasMore));
|
|
780
|
+
}
|
|
781
|
+
} else {
|
|
782
|
+
setRows(nextRows);
|
|
783
|
+
}
|
|
518
784
|
} catch (loadError) {
|
|
519
785
|
setError(loadError?.message || `Could not load ${isChat ? 'live chat' : 'the programme guide'}.`);
|
|
520
786
|
} finally {
|
|
521
787
|
setLoading(false);
|
|
522
788
|
}
|
|
523
|
-
}, [channelId, integration, isChat]);
|
|
789
|
+
}, [channelId, integration, isChat, pageSize]);
|
|
524
790
|
|
|
525
791
|
useEffect(() => {
|
|
526
792
|
let cancelled = false;
|
|
@@ -534,16 +800,36 @@ function WebIntegrationPanel({ kind, integration, integrations, source, title, t
|
|
|
534
800
|
|
|
535
801
|
useEffect(() => {
|
|
536
802
|
setLoading(true);
|
|
803
|
+
rowsRef.current = [];
|
|
804
|
+
setRows([]);
|
|
805
|
+
setMessageOffset(0);
|
|
806
|
+
setHasMoreMessages(false);
|
|
537
807
|
load();
|
|
538
808
|
if (!isChat) return undefined;
|
|
539
809
|
const timer = setInterval(load, Math.max(1000, Number(integration.pollIntervalMs) || 5000));
|
|
540
810
|
return () => clearInterval(timer);
|
|
541
811
|
}, [load, isChat, integration.pollIntervalMs]);
|
|
542
812
|
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
813
|
+
const loadOlderMessages = async () => {
|
|
814
|
+
if (!isChat || !hasMoreMessages || loadingOlder) return;
|
|
815
|
+
setLoadingOlder(true);
|
|
816
|
+
try {
|
|
817
|
+
setError('');
|
|
818
|
+
const value = await integration.loadMessages({ channelId, limit: pageSize, offset: messageOffset });
|
|
819
|
+
const olderRows = responseRows(value, ['messages', 'items', 'comments']);
|
|
820
|
+
updateRows(mergeChatMessages(rowsRef.current, olderRows, true));
|
|
821
|
+
setMessageOffset((offset) => offset + olderRows.length);
|
|
822
|
+
const explicitHasMore = value?.hasMore ?? value?.pagination?.hasMore;
|
|
823
|
+
setHasMoreMessages(explicitHasMore === undefined ? olderRows.length >= pageSize : Boolean(explicitHasMore));
|
|
824
|
+
} catch (loadError) {
|
|
825
|
+
setError(loadError?.message || 'Could not load more messages.');
|
|
826
|
+
} finally {
|
|
827
|
+
setLoadingOlder(false);
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
|
|
831
|
+
const send = async (comment) => {
|
|
832
|
+
if (!comment || sending || !integration.sendMessage) return false;
|
|
547
833
|
setSending(true);
|
|
548
834
|
try {
|
|
549
835
|
await integration.sendMessage({
|
|
@@ -552,10 +838,11 @@ function WebIntegrationPanel({ kind, integration, integrations, source, title, t
|
|
|
552
838
|
username: user?.username || 'Viewer',
|
|
553
839
|
comment,
|
|
554
840
|
});
|
|
555
|
-
setMessage('');
|
|
556
841
|
await load();
|
|
842
|
+
return true;
|
|
557
843
|
} catch (sendError) {
|
|
558
844
|
setError(sendError?.message || 'Could not send your message.');
|
|
845
|
+
return false;
|
|
559
846
|
} finally {
|
|
560
847
|
setSending(false);
|
|
561
848
|
}
|
|
@@ -572,29 +859,14 @@ function WebIntegrationPanel({ kind, integration, integrations, source, title, t
|
|
|
572
859
|
loading ? h('div', { className: 'cinecrew-player__panel-state' }, 'Loading…') : null,
|
|
573
860
|
error ? h('div', { className: 'cinecrew-player__panel-state is-error', role: 'status' }, error) : null,
|
|
574
861
|
!loading && !error && rows.length === 0 ? h('div', { className: 'cinecrew-player__panel-state' }, emptyStateMessage) : null,
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
const end = row.endMs ?? row.end ?? row.endTime;
|
|
584
|
-
return h('article', { key, className: 'cinecrew-player__epg-item' },
|
|
585
|
-
h('small', null, [formatListingTime(start), formatListingTime(end)].filter(Boolean).join(' – ')),
|
|
586
|
-
h('strong', null, row.title || row.name || 'Programme'),
|
|
587
|
-
row.description ? h('span', null, row.description) : null);
|
|
588
|
-
})),
|
|
589
|
-
isChat && typeof integration.sendMessage === 'function' ? h('form', { className: 'cinecrew-player__chat-form', onSubmit: send },
|
|
590
|
-
h('input', {
|
|
591
|
-
value: message,
|
|
592
|
-
onChange: (event) => setMessage(event.target.value),
|
|
593
|
-
placeholder: 'Add a message…',
|
|
594
|
-
'aria-label': 'Chat message',
|
|
595
|
-
maxLength: 1000,
|
|
596
|
-
}),
|
|
597
|
-
h('button', { type: 'submit', disabled: sending || !message.trim() }, sendButtonLabel)) : null);
|
|
862
|
+
h('div', { className: 'cinecrew-player__panel-list' },
|
|
863
|
+
isChat && hasMoreMessages ? h(WebLoadMoreMessages, { loading: loadingOlder, onClick: loadOlderMessages }) : null,
|
|
864
|
+
rows.map((row, index) => h(WebPanelRow, { key: getChatMessageKey(row, index), row, index, isChat }))),
|
|
865
|
+
isChat ? h(WebChatComposer, {
|
|
866
|
+
sending,
|
|
867
|
+
canSend: typeof integration.sendMessage === 'function',
|
|
868
|
+
onSend: send,
|
|
869
|
+
}) : null);
|
|
598
870
|
}
|
|
599
871
|
|
|
600
872
|
/**
|
|
@@ -614,6 +886,10 @@ export const CineCrewPlayer = forwardRef(function CineCrewPlayer(props, ref) {
|
|
|
614
886
|
playbackRate: playbackRateProp = 1,
|
|
615
887
|
paused: pausedProp,
|
|
616
888
|
controls: controlOverrides = {},
|
|
889
|
+
features = {},
|
|
890
|
+
drawerMode = 'overlay',
|
|
891
|
+
drawerStyle,
|
|
892
|
+
messagePageSize = 50,
|
|
617
893
|
actions = {},
|
|
618
894
|
theme: themeProp = {},
|
|
619
895
|
icons = {},
|
|
@@ -634,7 +910,6 @@ export const CineCrewPlayer = forwardRef(function CineCrewPlayer(props, ref) {
|
|
|
634
910
|
onPromotePreview,
|
|
635
911
|
initialShowLiveChat = false,
|
|
636
912
|
liveChatNonce = 0,
|
|
637
|
-
onMinimize,
|
|
638
913
|
onBack,
|
|
639
914
|
mediaId,
|
|
640
915
|
onFullscreen,
|
|
@@ -758,6 +1033,7 @@ export const CineCrewPlayer = forwardRef(function CineCrewPlayer(props, ref) {
|
|
|
758
1033
|
pausedRef: pausedStateRef,
|
|
759
1034
|
onErrorRef,
|
|
760
1035
|
});
|
|
1036
|
+
const streamMode = getStreamMode(youtubeVideoId, mpegTs.useMpegTs, useHls);
|
|
761
1037
|
useWebAc3AudioPlayback({
|
|
762
1038
|
active: mpegTs.useAc3Fallback,
|
|
763
1039
|
streamUrl,
|
|
@@ -772,7 +1048,9 @@ export const CineCrewPlayer = forwardRef(function CineCrewPlayer(props, ref) {
|
|
|
772
1048
|
if (!video) return;
|
|
773
1049
|
if (isPaused) video.pause();
|
|
774
1050
|
else video.play().catch((playError) => {
|
|
775
|
-
if (playError?.name !== 'NotAllowedError'
|
|
1051
|
+
if (playError?.name !== 'NotAllowedError' && playError?.name !== 'AbortError') {
|
|
1052
|
+
handleError(playError);
|
|
1053
|
+
}
|
|
776
1054
|
});
|
|
777
1055
|
}, [isPaused, streamUrl, youtubeVideoId, handleError]);
|
|
778
1056
|
|
|
@@ -826,7 +1104,6 @@ export const CineCrewPlayer = forwardRef(function CineCrewPlayer(props, ref) {
|
|
|
826
1104
|
else youtubeRef.current?.seekTo?.(Math.max(0, Number(seconds) || 0));
|
|
827
1105
|
}, { seconds: Number(seconds) || 0 }), [action]);
|
|
828
1106
|
const handleBack = useCallback(() => action('onBack', onBack || props.onClose, { title, source: media }), [action, onBack, props.onClose, title, media]);
|
|
829
|
-
const handleMinimize = useCallback(() => action('onMinimize', onMinimize, { title, source: media }), [action, onMinimize, title, media]);
|
|
830
1107
|
useImperativeHandle(ref, () => {
|
|
831
1108
|
const api = {
|
|
832
1109
|
play: () => setPaused(false),
|
|
@@ -847,7 +1124,6 @@ export const CineCrewPlayer = forwardRef(function CineCrewPlayer(props, ref) {
|
|
|
847
1124
|
seekTo,
|
|
848
1125
|
seekBy: (delta) => seekTo((Number(videoRef.current?.currentTime) || currentTime) + (Number(delta) || 0)),
|
|
849
1126
|
back: handleBack,
|
|
850
|
-
minimize: handleMinimize,
|
|
851
1127
|
getVideoElement: () => videoRef.current,
|
|
852
1128
|
enterFullscreen: () => playerRef.current?.requestFullscreen?.(),
|
|
853
1129
|
exitFullscreen: () => typeof document !== 'undefined' ? document.exitFullscreen?.() : undefined,
|
|
@@ -855,11 +1131,12 @@ export const CineCrewPlayer = forwardRef(function CineCrewPlayer(props, ref) {
|
|
|
855
1131
|
};
|
|
856
1132
|
publicPlayerRef.current = api;
|
|
857
1133
|
return api;
|
|
858
|
-
}, [setPaused, togglePlay, restart, toggleMute, setMuted, setAspectRatio, setAudioOnlyMode, setVideoOnlyMode, setPlaybackRateAction, seekTo, handleBack,
|
|
1134
|
+
}, [setPaused, togglePlay, restart, toggleMute, setMuted, setAspectRatio, setAudioOnlyMode, setVideoOnlyMode, setPlaybackRateAction, seekTo, handleBack, availableTracks, tracksProp, currentTime]);
|
|
859
1135
|
|
|
860
1136
|
const hasChat = typeof integrations.liveChat?.loadMessages === 'function' || typeof renderLiveChat === 'function' || typeof integrations.liveChat?.render === 'function' || typeof actions.onLiveChatOpen === 'function';
|
|
861
1137
|
const hasEpg = typeof integrations.epg?.loadListings === 'function' || typeof renderEpg === 'function' || typeof integrations.epg?.render === 'function' || typeof actions.onEpgOpen === 'function';
|
|
862
1138
|
const hasRecording = !!integrations.recording || typeof actions.onRecordingStart === 'function';
|
|
1139
|
+
const hasDiagnostics = Boolean(features.diagnostics) || typeof actions.onDiagnosticsOpen === 'function';
|
|
863
1140
|
const sourceType = String(media.type || media.mimeType || '').toLowerCase();
|
|
864
1141
|
const directVideoSource = getDirectVideoSource({
|
|
865
1142
|
mpegTs: mpegTs.useMpegTs,
|
|
@@ -948,10 +1225,11 @@ export const CineCrewPlayer = forwardRef(function CineCrewPlayer(props, ref) {
|
|
|
948
1225
|
actions,
|
|
949
1226
|
onBack: handleBack,
|
|
950
1227
|
backVisible: typeof actions.onBack === 'function' || typeof onBack === 'function' || typeof props.onClose === 'function',
|
|
951
|
-
minimizeVisible: typeof actions.onMinimize === 'function' || typeof onMinimize === 'function',
|
|
952
1228
|
isLive,
|
|
953
1229
|
restart,
|
|
954
1230
|
toggleLock,
|
|
1231
|
+
toggleMute,
|
|
1232
|
+
muted,
|
|
955
1233
|
hasRecording,
|
|
956
1234
|
recording,
|
|
957
1235
|
integrations,
|
|
@@ -962,8 +1240,8 @@ export const CineCrewPlayer = forwardRef(function CineCrewPlayer(props, ref) {
|
|
|
962
1240
|
activePanel,
|
|
963
1241
|
hasChat,
|
|
964
1242
|
hasEpg,
|
|
1243
|
+
hasDiagnostics,
|
|
965
1244
|
handleOpenPanel: openPanel,
|
|
966
|
-
handleMinimize,
|
|
967
1245
|
setRecording,
|
|
968
1246
|
});
|
|
969
1247
|
const webPanel = createWebPanelNode({
|
|
@@ -975,6 +1253,14 @@ export const CineCrewPlayer = forwardRef(function CineCrewPlayer(props, ref) {
|
|
|
975
1253
|
title,
|
|
976
1254
|
theme,
|
|
977
1255
|
onClose: () => setActivePanel(null),
|
|
1256
|
+
diagnostics: {
|
|
1257
|
+
streamMode,
|
|
1258
|
+
status: getPlaybackStatus(error, buffering, isPaused),
|
|
1259
|
+
currentTime,
|
|
1260
|
+
duration,
|
|
1261
|
+
videoRef,
|
|
1262
|
+
},
|
|
1263
|
+
messagePageSize,
|
|
978
1264
|
});
|
|
979
1265
|
|
|
980
1266
|
const handleYouTubeProgress = (event) => {
|
|
@@ -996,6 +1282,7 @@ export const CineCrewPlayer = forwardRef(function CineCrewPlayer(props, ref) {
|
|
|
996
1282
|
playbackRate,
|
|
997
1283
|
videoOnly,
|
|
998
1284
|
videoStyle,
|
|
1285
|
+
drawerResize: Boolean(activePanel) && drawerMode === 'resize',
|
|
999
1286
|
audioOnly,
|
|
1000
1287
|
inlinePreview,
|
|
1001
1288
|
onPromotePreview,
|
|
@@ -1008,8 +1295,6 @@ export const CineCrewPlayer = forwardRef(function CineCrewPlayer(props, ref) {
|
|
|
1008
1295
|
onError: handleError,
|
|
1009
1296
|
handleError,
|
|
1010
1297
|
});
|
|
1011
|
-
const streamMode = getStreamMode(youtubeVideoId, mpegTs.useMpegTs, useHls);
|
|
1012
|
-
|
|
1013
1298
|
const bottomControlProps = {
|
|
1014
1299
|
isLive,
|
|
1015
1300
|
overrides: controlOverrides,
|
|
@@ -1063,6 +1348,8 @@ export const CineCrewPlayer = forwardRef(function CineCrewPlayer(props, ref) {
|
|
|
1063
1348
|
togglePlay,
|
|
1064
1349
|
bottomControlProps,
|
|
1065
1350
|
onWheel: (event) => onInlinePreviewWheel?.(event.deltaY),
|
|
1351
|
+
drawerMode,
|
|
1352
|
+
drawerStyle,
|
|
1066
1353
|
});
|
|
1067
1354
|
});
|
|
1068
1355
|
|
|
@@ -1111,7 +1398,7 @@ export const InlineLivePlayer = React.memo(function InlineLivePlayer({
|
|
|
1111
1398
|
controls: {
|
|
1112
1399
|
back: false, restart: false, lock: false, recording: false, liveChat: false, epg: false,
|
|
1113
1400
|
seek: false, aspectRatio: false, videoOnly: false, audioOnly: false, audioTracks: false,
|
|
1114
|
-
playbackRate: false,
|
|
1401
|
+
playbackRate: false, playPause: true, mute: true, fullscreen: true,
|
|
1115
1402
|
...controls,
|
|
1116
1403
|
},
|
|
1117
1404
|
actions: {
|