Skip to content

Commit 785dd49

Browse files
committed
refactor: streamline video player module structure and enhance type definitions for improved maintainability
1 parent 15f4ef4 commit 785dd49

File tree

26 files changed

+546
-1031
lines changed

26 files changed

+546
-1031
lines changed

examples/javascript/src/playlist.ts

Lines changed: 0 additions & 506 deletions
Large diffs are not rendered by default.

packages/video-player/javascript/index.ts

Lines changed: 75 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import videojs from 'video.js';
1+
import videojs, { type Player as VideoJsPlayer } from 'video.js';
22
import PluginType from 'video.js/dist/types/plugin';
33
import './modules/http-source-selector/plugin';
44
import './modules/context-menu/plugin';
5-
import type { IKPlayerOptions, RemoteTextTrackOptions } from './interfaces';
6-
import type Player from 'video.js/dist/types/player';
5+
import type { IKPlayerOptions, RemoteTextTrackOptions, Player } from './interfaces';
76
import type { SourceOptions } from './interfaces';
87
import type { AugmentedSourceOptions } from './interfaces/AugementedSourceOptions';
98

@@ -16,6 +15,7 @@ import { ShoppableManager } from './modules/shoppable/shoppable-manager';
1615
import { prepareSource, normalizeInput, waitForVideoReady, preparePosterSrc, validateIKPlayerOptions, prepareChaptersVttSrc, CleanupRegistry } from './utils';
1716
import { enableFloatingPlayer } from './modules/floating-player';
1817
import './modules/logo-button';
18+
1919
const defaults: IKPlayerOptions = {
2020
imagekitId: 'random_id',
2121
floatingWhenNotVisible: null,
@@ -248,6 +248,10 @@ this.player.ready(() => {
248248

249249
private overrideSrc() {
250250
const nativeSrc = this.player.src.bind(this.player);
251+
const ensurePrepared = (src: AugmentedSourceOptions): NonNullable<AugmentedSourceOptions['prepared']> => {
252+
if (!src.prepared) src.prepared = {};
253+
return src.prepared;
254+
};
251255

252256
this.player.src = (raw: string | SourceOptions | Array<string | SourceOptions> | undefined) => {
253257
// increment the version on each call
@@ -286,18 +290,8 @@ this.player.ready(() => {
286290
const { maxTries, videoTimeoutInMS, delayInMS } = this.ikGlobalSettings_;
287291
// set prepared.src
288292
inputs.forEach((src) => {
289-
// @ts-ignore
290-
if (!this.hasPreparedSrc(src)) {
291-
// if src is not prepared, set the src directly
292-
293-
// @ts-ignore
294-
if (!src.prepared)
295-
{
296-
// @ts-ignore
297-
src.prepared = {}
298-
}
299-
// @ts-ignore
300-
src.prepared.src = prepared[0].src;
293+
if (typeof src === 'object' && src !== null && !this.hasPreparedSrc(src)) {
294+
ensurePrepared(src as AugmentedSourceOptions).src = prepared[0].src;
301295
}
302296
});
303297

@@ -323,29 +317,20 @@ this.player.ready(() => {
323317
? this.currentSource_[0]
324318
: this.currentSource_;
325319
// if poster is already prepared, use it directly
326-
// @ts-ignore
327-
if (currentSource_?.prepared?.poster) {
328-
console.log("Using prepared poster src")
329-
// @ts-ignore
330-
this.player.poster(currentSource_?.prepared?.poster);
320+
const preparedPoster = (currentSource_ as AugmentedSourceOptions | null | undefined)?.prepared?.poster;
321+
if (preparedPoster) {
322+
this.player.poster(preparedPoster);
331323
}
332324
else {
333325
preparePosterSrc(currentSource_, this.ikGlobalSettings_).then(
334326
poster => {
335-
// if preparedPosterSrc is not empty, set it
336-
// @ts-ignore
337-
// currentSource_.preparedPosterSrc = poster;
338327
// set the poster on the player
339328
if (poster) {
340329
this.player.poster(poster);
341330
}
342-
// @ts-ignore
343-
if (!currentSource_.prepared) {
344-
// @ts-ignore
345-
currentSource_.prepared = {};
331+
if (currentSource_) {
332+
ensurePrepared(currentSource_ as AugmentedSourceOptions).poster = poster ?? undefined;
346333
}
347-
// @ts-ignore
348-
currentSource_.prepared.poster = poster;
349334
}
350335
).catch(err => {
351336
this.player.error(err.message);
@@ -399,14 +384,6 @@ this.player.ready(() => {
399384
this.player.log.warn(`Default VTT fetch failed with status: (${res.status}); skipping chapters.`);
400385
return;
401386
}
402-
// add chapters track
403-
// @todo commented this. You are overriding remoteTextTrack. Verify the correctness of this.
404-
// const chaptersTrack = this.player.addRemoteTextTrack({
405-
// kind: 'chapters',
406-
// label: 'Chapters',
407-
// src: 'https://ik.imagekit.io/zuqlyov9d/chapters.vtt',
408-
// default: false,
409-
// }, false);
410387
const data = await res.text();
411388
chapterList = parseChaptersFromVTT(data);
412389
} catch (e) {
@@ -432,7 +409,6 @@ this.player.ready(() => {
432409
if (!src.recommendations) return;
433410

434411
const overlay = this.player.getChild('RecommendationsOverlay');
435-
console.log('RecommendationsOverlay:', overlay);
436412
if (overlay) overlay.dispose();
437413
this.player.addChild('RecommendationsOverlay', { recommendations: src.recommendations, playerOptions: this.ikGlobalSettings_ });
438414
}
@@ -493,63 +469,62 @@ export function videoPlayer(
493469
element: string | HTMLElement,
494470
options: IKPlayerOptions,
495471
videoJsOptions: any = {}
496-
) {
497-
const player = videojs(element, {
498-
...videoJsOptions,
499-
// playbackRates: [0.5, 1, 1.5, 2],
500-
// children: {
501-
// controlBar: {
502-
// fullscreenToggle: false,
503-
// pictureInPictureToggle: false,
504-
// volumePanel: false,
505-
// playbackRateMenuButton: false,
506-
// }
507-
// },
508-
// controls: false,
509-
// autoplay: true,
510-
// aspectRatio: '9:16',
472+
): Player {
473+
// Keep this for reference
474+
// videoJsOptions = {
475+
// playbackRates: [0.5, 1, 1.5, 2],
476+
// children: {
477+
// controlBar: {
478+
// fullscreenToggle: false,
479+
// pictureInPictureToggle: false,
480+
// volumePanel: false,
481+
// playbackRateMenuButton: false,
482+
// }
483+
// },
484+
// controls: false,
485+
// autoplay: true,
486+
// aspectRatio: '9:16',
511487

512-
// responsive: true,
513-
// breakpoints: {
514-
// // tiny: 300,
515-
// // xsmall: 400,
516-
// // small: 500,
517-
// // medium: 600,
518-
// // large: 700,
519-
// // xlarge: 800,
520-
// huge: 900
521-
// },
522-
// controlBar: {
523-
// skipButtons: {
524-
// forward: 10
525-
// }
526-
// },
527-
488+
// responsive: true,
489+
// breakpoints: {
490+
// // tiny: 300,
491+
// // xsmall: 400,
492+
// // small: 500,
493+
// // medium: 600,
494+
// // large: 700,
495+
// // xlarge: 800,
496+
// huge: 900
497+
// },
498+
// controlBar: {
499+
// skipButtons: {
500+
// forward: 10
501+
// }
502+
// },
503+
// }
504+
const player: VideoJsPlayer = videojs(element, {
505+
...videoJsOptions,
528506
html5: { nativeTextTracks: false },
529507
plugins: {
530508
...(videoJsOptions.plugins ?? {}),
531509
httpSourceSelector: { default: 'auto' },
532510
imagekitVideoPlayer: options,
533511
},
534512
});
535-
// @ts-ignore
536-
player.httpSourceSelector();
537-
// @ts-ignore
538-
// Explicitly handle both cases for the context menu
513+
514+
// Handle context menu visibility
539515
if (options.hideContextMenu === true) {
540516
// If hiding is requested, add a listener that ONLY prevents the default menu.
541517
// This will disable all right-click menus on the player.
542518
player.on('contextmenu', (e: Event) => {
543519
e.preventDefault();
544520
});
545521
} else {
546-
// Otherwise, set up our custom, dynamic context menu.
547-
522+
// Initialize context menu plugin only when not hidden
548523
/**
549524
* Helper function to generate the context menu content
550525
* based on the player's current state.
551526
*/
552-
const createContextMenuContent = () => {
527+
const createContextMenuContent = (player: Player) => {
553528
return [{
554529
label: player.paused() ? "Play" : "Pause",
555530
listener: function () {
@@ -584,23 +559,33 @@ export function videoPlayer(
584559
}];
585560
};
586561

587-
// Initialize the context menu with the initial content.
588-
// The contextmenuUI plugin internally handles preventDefault for this case.
589-
// @ts-ignore
590-
player.contextmenuUI({
591-
content: createContextMenuContent()
592-
});
562+
// Check if plugin is available
563+
const hasContextMenuUIMethod = typeof (player as any).contextmenuUI === 'function';
593564

594-
// Add an event listener to update the menu content on every right-click
595-
player.on('contextmenu', () => {
596-
// @ts-ignore
597-
player.contextmenuUI({
598-
content: createContextMenuContent()
599-
});
600-
});
565+
if (!hasContextMenuUIMethod) {
566+
console.error('[ImageKit Video Player] ERROR: contextmenuUI method not found on player!');
567+
console.error('[ImageKit Video Player] Available plugins:', Object.keys(videojs.getPlugins()));
568+
} else {
569+
try {
570+
(player as any).contextmenuUI({
571+
createContextMenuContent: createContextMenuContent
572+
});
573+
} catch (error) {
574+
console.error('[ImageKit Video Player] ERROR initializing context menu plugin:', error);
575+
}
576+
}
577+
}
578+
579+
// Verify that plugin augmentation completed successfully
580+
// The imagekitVideoPlayer plugin adds methods (playlist, src override, etc.) at runtime
581+
if (!('playlist' in player) || typeof (player as any).playlist !== 'function') {
582+
throw new Error('ImageKit video player plugin failed to initialize: playlist method not found');
601583
}
602584

603-
return player;
585+
// Type assertion: player has augmented methods added by the plugin at runtime
586+
// Using double assertion (as unknown as Player) because VideoJsPlayer and Player
587+
// don't have sufficient type overlap (playlist method is added at runtime)
588+
return player as unknown as Player;
604589
}
605590

606591
export * from './interfaces';

packages/video-player/javascript/interfaces/Player.ts

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
1-
import Player from 'video.js/dist/types/player';
2-
import type { Transformation }from '@imagekit/javascript'
1+
import type BasePlayer from 'video.js/dist/types/player';
2+
import type { Transformation } from '@imagekit/javascript';
3+
import type { SourceOptions } from './SourceOptions';
4+
import type { PlaylistOptions } from './Playlist';
5+
import type { PlaylistManager } from '../modules/playlist/playlist-manager';
6+
import type { RemoteTextTrackOptions } from './TextTrack';
37

8+
/**
9+
* Interface for the ImageKit Video Player plugin instance.
10+
* This is returned when calling player.imagekitVideoPlayer() without arguments.
11+
*/
12+
export interface ImageKitVideoPlayerPluginInstance {
13+
getPlaylistManager(): PlaylistManager | undefined;
14+
getOriginalCurrentSource(): SourceOptions | SourceOptions[] | null;
15+
getPlayerOptions(): IKPlayerOptions;
16+
}
417

518
export interface IKPlayerOptions {
619
/** Your ImageKit ID */
@@ -38,4 +51,49 @@ export interface IKPlayerOptions {
3851
signerFn?: (src: string) => Promise<string>;
3952
}
4053

54+
/**
55+
* Interface for ImageKit-specific Player methods.
56+
* This interface defines method overloads that prioritize ImageKit signatures.
57+
*/
58+
interface ImageKitPlayerMethods {
59+
/**
60+
* Overridden src method that accepts ImageKit SourceOptions.
61+
* This allows passing enhanced options like chapters, transformations, etc.
62+
* The method signature is overridden to accept SourceOptions instead of the base Video.js src format.
63+
*
64+
* @overload ImageKit-specific signature
65+
*/
66+
src(raw?: string | SourceOptions | Array<string | SourceOptions>): void;
67+
/**
68+
* Overridden addRemoteTextTrack method that accepts ImageKit RemoteTextTrackOptions.
69+
* This allows passing enhanced options like auto-generated subtitles, translations, etc.
70+
* The method signature is overridden to accept RemoteTextTrackOptions instead of the base Video.js text track format.
71+
*
72+
* @overload ImageKit-specific signature
73+
*/
74+
addRemoteTextTrack(options: RemoteTextTrackOptions, manualCleanup?: boolean): HTMLTrackElement | void;
75+
/**
76+
* Initialize the ImageKit Video Player plugin with options.
77+
* @param options - ImageKit player configuration options
78+
*/
79+
imagekitVideoPlayer(options: IKPlayerOptions): void;
80+
/**
81+
* Get the ImageKit Video Player plugin instance.
82+
* Returns the plugin instance when called without arguments.
83+
*/
84+
imagekitVideoPlayer(): ImageKitVideoPlayerPluginInstance;
85+
playlist(options: {
86+
sources?: SourceOptions[];
87+
options?: PlaylistOptions;
88+
}): PlaylistManager;
89+
}
4190

91+
/**
92+
* Augmented Player type that includes ImageKit-specific methods.
93+
* This type extends the base Video.js Player with additional functionality.
94+
*
95+
* Note: Method overrides (src, addRemoteTextTrack) create overloads with both
96+
* the base Video.js signatures and ImageKit-specific signatures. TypeScript will
97+
* use the most specific matching signature when calling these methods.
98+
*/
99+
export type Player = BasePlayer & ImageKitPlayerMethods;

packages/video-player/javascript/modules/chapters/chapterMarkerProgressBar.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,15 @@ class ChapterMarkersProgressBarControl extends Component {
7373
* Attaches mouse move and leave handlers to the main progress control.
7474
*/
7575
attachHoverHandlers(player: Player) {
76-
// @ts-ignore
77-
const progressControl = player.controlBar.progressControl;
76+
// Access controlBar.progressControl via type assertion (Video.js internal API)
77+
const playerWithControlBar = player as unknown as {
78+
controlBar: {
79+
progressControl: {
80+
el(): HTMLElement;
81+
};
82+
};
83+
};
84+
const progressControl = playerWithControlBar.controlBar.progressControl;
7885

7986
const mousemoveHandler = (e: MouseEvent) => {
8087
if (!this.chapterTooltipContainer) return;

0 commit comments

Comments
 (0)