/** * Custom HTML element for Movi Player * Usage: * * Note: Custom element names must contain a hyphen per HTML spec. * * Supports native video element properties: * - src, autoplay, controls, loop, muted, playsinline, preload, poster * - width, height, crossorigin * - volume, playbackRate, currentTime, duration, paused, ended */ import type { RendererType } from "../types"; import type { SourceAdapter } from "../source/SourceAdapter"; import { type QoESink, type QoESession } from "../utils/QoE"; export declare class MoviElement extends HTMLElement { private canvas; private video; private subtitleOverlay; private _captionLive; private _captionObserver; private _lastCaptionText; private _qoe; private _qoeHeartbeat; private player; private isLoading; private _isUnsupported; private eventHandlers; private controlsContainer; private unmuteOverlay; private _userHasUnmuted; private brokenIndicator; private emptyStateIndicator; private coverArtOverlay; private coverArtCanvas; private coverArtBitmap; private _posterCoverBitmap; private _posterCoverUrl; private _posterCoverLoading; private _coverArtBgEl; private _coverArtBgUrl; private _coverArtResolved; private _lastStripDispatched; private controlsTimeout; private isOverControls; private isSeeking; private pendingSeekTarget; private _pendingSeek; private isDragging; private isTouchDragging; private touchStartX; private touchStartY; private touchStartTime; private gesturePerformed; private clickTimer; private lastSeekTime; private lastSeekSide; private cumulativeSeekAmount; private _seekChainTarget; private _contextMenuVisible; private _contextMenuJustClosed; private _menuPortalHost; private _menuPortalRoot; private _menuHome; private lastTouchTime; private _holdSpeedTimer; private _holdSpeedActive; private _rateBeforeHold; private _openMenuViaGear; private _mediaSessionReady; private _mediaSessionLastPos; private _mediaSessionArtworkUrl; private _nerdStatsVisible; private _currentManualRotation; private _timelineGenerating; private _timelineCancelled; private _timelineComplete; private _timelineNextIndex; private nerdStatsInterval; private networkSpeedHistory; private _stutterInterval; private _stutterLastPresented; private _stutterSeconds; private _stutterCooldown; private _stutterCooldownTimer; private static readonly GRAPH_MAX_SAMPLES; private _src; private _sourceAdapter; private _headers; private _audioOnly; private _audioSrc; private _videoQualities; private _audioTracks; private _subtitleTracks; private _autoplay; private _autoplayStarting; private _autoplayPendingVisible; private _autoMutedForAutoplay; private _hasEverPlayed; private _loopRestartInFlight; private _pendingPlay; private _preloadGateActive; private _resumeDialogPending; private _posterTime; private _generatedPosterUrl; private _posterGenId; private _controls; private _loop; private _muted; private _playsinline; private _preload; private _poster; private _volume; private _playbackRate; private _subtitleDelay; private _subtitleSettings; private _ambientMode; private _renderer; private _objectFit; private _currentFit; private _thumb; private _linearMode; private _hdr; private _theme; private _sw; private _swForcedForCurrentSource; private _suppressSwReload; private _fps; private _gesturefs; private _noHotkeys; private _startAt; private _fastSeek; private _doubleTap; private _themeColor; private _bufferSize; private _title; private _showTitle; private _resume; private _stableVolume; private _audioOutputDeviceId; private _audioOutputs; private _audioOutputsBound; private _vr360; private _vrPointerDown; private _vrMoved; private _vrLastX; private _vrLastY; private _vrPinchDist; private _aspectPinchDist; private _vrSuppressClick; private _vrPadDragging; private _encrypted; private _tokenUrl; private _videoUrl; private _videoId; private _resumeSaveInterval; private _posterSeekActive; private _titleAutoLoaded; private _resumeCheckedWithTitle; private _stripTitleAttr; private _lastDuration; private posterElement; private _ambientWrapper; private ambientWrapperElement; private _ambientRafId; private _lastAmbientSampleTime; private _lastRateChangeTime; private static readonly AMBIENT_RATE_CHANGE_COOLDOWN_MS; private _ambientSampleInterval; private currentAmbientColors; private _contextLostTime; private _contextLostPlaying; private _lastFrameSnapshot; private _snapshotPosterActive; private _snapshotPosterPrev; private _showSnapshotPoster; private _hideSnapshotPoster; private _onVisibilityChange; static get observedAttributes(): string[]; constructor(); private createContextMenu; private createControls; private setupControlHandlers; private seekFromEvent; private seekFromTouchEvent; /** Begin press-and-hold fast playback (2x). No-op unless actively playing, so * a long-press while paused doesn't silently change the saved rate. */ private startHoldSpeed; /** End press-and-hold fast playback, restoring the pre-hold rate. */ private stopHoldSpeed; private setupGestures; /** * 360° look-around for mouse + trackpad. Touch is handled inside * setupGestures (it already owns touchstart/move). These listeners no-op * unless 360° mode is active, and a drag past a few pixels suppresses the * trailing click so dragging never toggles play/pause. */ private setupVRControls; /** * Enable/disable VR rendering and pick the format: * - half: VR180 (front hemisphere) vs full 360°. * - fisheye: equidistant fisheye vs equirectangular. * - sbs: side-by-side stereo (render the left eye) vs mono. * Forwards to the player's renderer and flips the host class (for cursor + * touch-action CSS). There's no toggle button: VR turns on automatically for * sources we know are 360/180 (spherical metadata or the `vr` attribute), the * same way YouTube treats them. */ setVR360(enabled: boolean, half?: boolean, fisheye?: boolean, sbs?: boolean, stereographic?: boolean): void; /** * When a custom/generated poster image is showing and 360° is on, draw the * poster onto the canvas through the equirectangular projection (so it looks * right, not flat-distorted) and hide the flat overlay. No-op for the * decoded-frame poster path (seek/postertime) — that already paints in 360. */ private renderVRPosterIfNeeded; /** * Resolve the VR format for the CURRENT source from spherical metadata * (track.projection) + the live `vr` attribute. Never carries the previous * clip's state over — switching to a normal clip drops back to flat unless * `vr` is still set. * * `vr` attribute tokens (space/comma separated), e.g. `vr="fisheye sbs"`: * (bare) / 360 → full 360°; 180 → VR180; sbs / 3d → side-by-side stereo * (left eye); fisheye → equidistant fisheye (implies 180). * * projection metadata: 0/undefined = none; 1 = equirectangular; * 3 = equirectangular-tile; 4 = half-equirectangular (VR180). Stereo/fisheye * aren't auto-surfaced, so those need the attribute. */ private resolveVRMode; /** Apply the resolved VR format to the renderer (enable OR disable). */ private detectAndApplyVR360; private setupKeyboardShortcuts; private setupContextMenu; private updateContextMenuContent; /** * Highlight the current fit as the active row in the aspect-ratio submenu, so * it reflects the live fit however it was last changed — context menu, the * bottom-controls aspect button, or the keyboard shortcut. Called each time * the fit submenu is shown: on hover (desktop, via showSubmenu) and on tap * (touch, via the action==="fit" branch). Kept OUT of updateContextMenuContent * (which runs on the main menu's open) to avoid touch-open side effects. */ private syncFitSubmenuActive; private setupSubmenuHover; private toggleFullscreen; private _pipWindow; private restorePiPCanvas; private togglePiP; /** Tracks host-driven fullscreen so toggleFullscreen() and the * movi-fullscreen-request event can reflect it correctly even when * document.fullscreenElement is null. */ private _hostFullscreen; /** CSS "fill the viewport" fallback used where the element Fullscreen API * isn't available (iOS Safari only exposes it for