Repository: cloudinary/cloudinary-video-player
Pull request #862: feat: live streams analytics
Author: login: jakub-roch
Created: 2025-05-12T09:20:50Z | Merged: 2025-05-13T08:42:55Z

Add new version of video analytics:
* use player adapter
* add live stream analytics


Diff:
diff --git a/package-lock.json b/package-lock.json
index 18ee8f5e3..c918807ec 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,7 +10,7 @@
       "license": "MIT",
       "dependencies": {
         "@cloudinary/url-gen": "^1.21.0",
-        "cloudinary-video-analytics": "1.7.1",
+        "cloudinary-video-analytics": "1.8.2",
         "cloudinary-video-player-profiles": "1.2.0",
         "lodash": "^4.17.21",
         "srt-parser-2": "^1.2.3",
@@ -5959,9 +5959,9 @@
       }
     },
     "node_modules/cloudinary-video-analytics": {
-      "version": "1.7.1",
-      "resolved": "https://registry.npmjs.org/cloudinary-video-analytics/-/cloudinary-video-analytics-1.7.1.tgz",
-      "integrity": "sha512-LIQjztFX8zakYT0fHaka1NTRDeEZPWbx65tqn6tK4t+2BMvrTjL6cmdYaJ8QpsiDJT7mwWtFnIMlLEkLmdHdsg==",
+      "version": "1.8.2",
+      "resolved": "https://registry.npmjs.org/cloudinary-video-analytics/-/cloudinary-video-analytics-1.8.2.tgz",
+      "integrity": "sha512-/jsmPU7D39Ajc3CGKyxJAEhAituHeZ6j/9GXAH0KHLXVi/kf6E1rD4j1XEE6iJq3j0NZ6+w0+O7d1MihA26CMg==",
       "dependencies": {
         "is-mobile": "^4.0.0",
         "uuid": "9.0.0"
diff --git a/package.json b/package.json
index ee61540c8..a627c9f98 100644
--- a/package.json
+++ b/package.json
@@ -79,7 +79,7 @@
       },
       {
         "path": "./lib/all.js",
-        "maxSize": "290kb"
+        "maxSize": "291kb"
       },
       {
         "path": "./lib/light/index.js",
@@ -89,7 +89,7 @@
   },
   "dependencies": {
     "@cloudinary/url-gen": "^1.21.0",
-    "cloudinary-video-analytics": "1.7.1",
+    "cloudinary-video-analytics": "1.8.2",
     "cloudinary-video-player-profiles": "1.2.0",
     "lodash": "^4.17.21",
     "srt-parser-2": "^1.2.3",
diff --git a/src/plugins/cloudinary-analytics/index.js b/src/plugins/cloudinary-analytics/index.js
index 83b91e647..27e62df50 100644
--- a/src/plugins/cloudinary-analytics/index.js
+++ b/src/plugins/cloudinary-analytics/index.js
@@ -1,4 +1,3 @@
-import videojs from 'video.js';
 import isPlainObject from 'lodash/isPlainObject';
 import { connectCloudinaryAnalytics } from 'cloudinary-video-analytics';
 import { PLAYER_EVENT } from '../../utils/consts';
@@ -6,11 +5,10 @@ import { PLAYER_EVENT } from '../../utils/consts';
 class CloudinaryAnalytics {
   constructor(player, options) {
     this.player = player;
-    this.shouldUseCustomEvents = videojs.browser.IS_IOS;
     this.cloudinaryAnalytics = connectCloudinaryAnalytics(this.player.videoElement, {
-      customEvents: this.shouldUseCustomEvents,
+      playerAdapter: this.getCloudinaryVideoPlayerAdapter(),
     });
-    this.currentVideMetadata = {
+    this.currentVideoMetadata = {
       cloudName: null,
       publicId: null
     };
@@ -22,45 +20,65 @@ class CloudinaryAnalytics {
     publicId: this.player.cloudinary.currentPublicId()
   });
 
-  sourceChanged = () => {
+  sourceChanged = (e, { source }) => {
     const metadata = this.getMetadata();
+
     if (metadata.cloudName && metadata.publicId) {
-      this.currentVideMetadata = metadata;
-      this.cloudinaryAnalytics.startManualTracking(metadata, {
+      const isLiveStream = source.resourceConfig().type === 'live';
+      this.currentVideoMetadata = metadata;
+      this.cloudinaryAnalytics.startManualTracking({
+        ...metadata,
+        ...(isLiveStream ? { type: 'live' } : {}),
+      }, {
         ...(isPlainObject(this.analyticsOptions) ? this.analyticsOptions : {}),
         videoPlayerType: 'cloudinary video player',
         videoPlayerVersion: VERSION,
       });
-    } else if (this.currentVideMetadata.cloudName !== metadata.cloudName || this.currentVideMetadata.publicId !== metadata.publicId) {
+    } else if (this.currentVideoMetadata.cloudName !== metadata.cloudName || this.currentVideoMetadata.publicId !== metadata.publicId) {
       this.cloudinaryAnalytics.stopManualTracking();
     }
   };
 
-  dispatchCustomEventOnVideoPlayer = (name, detail = {}) => {
-    const ev = new CustomEvent(`cld-custom-${name}`, {
-      detail,
-    });
-    this.player.videoElement.dispatchEvent(ev);
-  };
+  getCloudinaryVideoPlayerAdapter = () => {
+    const createCldVPEventListener = (eventName, callback) => {
+      this.player.on(eventName, callback);
+      return () => {
+        this.player.off(eventName, callback);
+      };
+    };
 
-  connectCustomEvents = () => {
-    this.player.on(PLAYER_EVENT.PLAY, () => this.dispatchCustomEventOnVideoPlayer('play'));
-    this.player.on(PLAYER_EVENT.PAUSE, () => this.dispatchCustomEventOnVideoPlayer('pause'));
-    this.player.on(PLAYER_EVENT.EMPTIED, () => this.dispatchCustomEventOnVideoPlayer('emptied'));
-    this.player.on(PLAYER_EVENT.LOADED_METADATA, () => {
-      const videoDuration = this.player.videoElement.duration || null;
-      this.dispatchCustomEventOnVideoPlayer('loadedmetadata', {
-        videoDuration,
-      });
-    });
+    return {
+      onCanPlay: (callback) => createCldVPEventListener('canplay', callback),
+      onCanPlayThrough: (callback) => createCldVPEventListener('canplaythrough', callback),
+      onComplete: (callback) => createCldVPEventListener('complete', callback),
+      onDurationChange: (callback) => createCldVPEventListener('durationchange', callback),
+      onEmptied: (callback) => createCldVPEventListener('emptied', callback),
+      onEnded: (callback) => createCldVPEventListener('ended', callback),
+      onError: (callback) => createCldVPEventListener('error', callback),
+      onLoadedData: (callback) => createCldVPEventListener('loadeddata', callback),
+      onLoadedMetadata: (callback) => createCldVPEventListener('loadedmetadata', callback),
+      onLoadStart: (callback) => createCldVPEventListener('loadstart', callback),
+      onPause: (callback) => createCldVPEventListener('pause', callback),
+      onPlay: (callback) => createCldVPEventListener('play', callback),
+      onPlaying: (callback) => createCldVPEventListener('playing', callback),
+      onProgress: (callback) => createCldVPEventListener('progress', callback),
+      onRateChange: (callback) => createCldVPEventListener('ratechange', callback),
+      onSeeked: (callback) => createCldVPEventListener('seeked', callback),
+      onSeeking: (callback) => createCldVPEventListener('seeking', callback),
+      onStalled: (callback) => createCldVPEventListener('stalled', callback),
+      onSuspend: (callback) => createCldVPEventListener('suspend', callback),
+      onTimeUpdate: (callback) => createCldVPEventListener('timeupdate', callback),
+      onVolumeChange: (callback) => createCldVPEventListener('volumechange', callback),
+      onWaiting: (callback) => createCldVPEventListener('waiting', callback),
+
+      getCurrentSrc: () => this.player.videoElement.currentSrc,
+      getCurrentTime: () => this.player.currentTime(),
+      getReadyState: () => this.player.videoElement.readyState,
+      getDuration: () => this.player.duration(),
+    };
   };
 
   init() {
-    // only for iOS there is problem with reporting events because videojs re-triggers events and stops native ones
-    if (this.shouldUseCustomEvents) {
-      this.connectCustomEvents();
-    }
-
     this.player.on(PLAYER_EVENT.CLD_SOURCE_CHANGED, this.sourceChanged);
   }
 }


Reviews:
user: login: kalifyaniv-cloudinary
body: Please check my second comment before merging... 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T11:02:32Z
user: login: jakub-roch
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T12:59:49Z
user: login: jakub-roch
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T07:30:46Z

Review comments:
diff_hunk: @@ -22,45 +20,65 @@ class CloudinaryAnalytics {
     publicId: this.player.cloudinary.currentPublicId()
   });
 
-  sourceChanged = () => {
+  sourceChanged = (e, { source }) => {
     const metadata = this.getMetadata();
+
     if (metadata.cloudName && metadata.publicId) {
-      this.currentVideMetadata = metadata;
-      this.cloudinaryAnalytics.startManualTracking(metadata, {
+      const isLiveStream = source.resourceConfig().type === 'live';
+      this.currentVideoMetadata = metadata;
+      this.cloudinaryAnalytics.startManualTracking({
+        ...metadata,
+        ...(isLiveStream ? { type: 'live' } : {}),
+      }, {
         ...(isPlainObject(this.analyticsOptions) ? this.analyticsOptions : {}),
         videoPlayerType: 'cloudinary video player',
         videoPlayerVersion: VERSION,
       });
-    } else if (this.currentVideMetadata.cloudName !== metadata.cloudName || this.currentVideMetadata.publicId !== metadata.publicId) {
+    } else if (this.currentVideoMetadata.cloudName !== metadata.cloudName || this.currentVideoMetadata.publicId !== metadata.publicId) {
       this.cloudinaryAnalytics.stopManualTracking();
     }
   };
 
-  dispatchCustomEventOnVideoPlayer = (name, detail = {}) => {
-    const ev = new CustomEvent(`cld-custom-${name}`, {
-      detail,
-    });
-    this.player.videoElement.dispatchEvent(ev);
-  };
+  getCloudinaryVideoPlayerAdapter = () => {
+    const createCldVPEventListener = (callback, eventName) => {
+      this.player.on(eventName, callback);
+      return () => {
+        this.player.off(eventName, callback);
+      };
+    };
 
-  connectCustomEvents = () => {
-    this.player.on(PLAYER_EVENT.PLAY, () => this.dispatchCustomEventOnVideoPlayer('play'));
-    this.player.on(PLAYER_EVENT.PAUSE, () => this.dispatchCustomEventOnVideoPlayer('pause'));
-    this.player.on(PLAYER_EVENT.EMPTIED, () => this.dispatchCustomEventOnVideoPlayer('emptied'));
-    this.player.on(PLAYER_EVENT.LOADED_METADATA, () => {
-      const videoDuration = this.player.videoElement.duration || null;
-      this.dispatchCustomEventOnVideoPlayer('loadedmetadata', {
-        videoDuration,
-      });
-    });
+    return {
+      onCanPlay: (callback) => createCldVPEventListener('canplay', callback),
+      onCanPlayThrough: (callback) => createCldVPEventListener('canplaythrough', callback),
+      onComplete: (callback) => createCldVPEventListener('complete', callback),
+      onDurationChange: (callback) => createCldVPEventListener('durationchange', callback),
+      onEmptied: (callback) => createCldVPEventListener('emptied', callback),
+      onEnded: (callback) => createCldVPEventListener('ended', callback),
+      onError: (callback) => createCldVPEventListener('error', callback),
+      onLoadedData: (callback) => createCldVPEventListener('loadeddata', callback),
+      onLoadedMetadata: (callback) => createCldVPEventListener('loadedmetadata', callback),
+      onLoadStart: (callback) => createCldVPEventListener('loadstart', callback),
+      onPause: (callback) => createCldVPEventListener('pause', callback),
+      onPlay: (callback) => createCldVPEventListener('play', callback),
+      onPlaying: (callback) => createCldVPEventListener('playing', callback),
+      onProgress: (callback) => createCldVPEventListener('progress', callback),
+      onRateChange: (callback) => createCldVPEventListener('ratechange', callback),
+      onSeeked: (callback) => createCldVPEventListener('seeked', callback),
+      onSeeking: (callback) => createCldVPEventListener('seeking', callback),
+      onStalled: (callback) => createCldVPEventListener('stalled', callback),
+      onSuspend: (callback) => createCldVPEventListener('suspend', callback),
+      onTimeUpdate: (callback) => createCldVPEventListener('timeupdate', callback),
+      onVolumeChange: (callback) => createCldVPEventListener('volumechange', callback),
+      onWaiting: (callback) => createCldVPEventListener('waiting', callback),
+
+      getCurrentSrc: () => this.player.videoElement.currentSrc,
+      getCurrentTime: () => this.player.currentTime(),
+      getReadyState: () => this.player.videoElement.readyState,
+      getDuration: () => this.player.duration(),
+    };
path: src/plugins/cloudinary-analytics/index.js
user: login: kalifyaniv-cloudinary
body: does this mean we added more events to the "regular" tracking also? 
created_at: 2025-05-12T11:01:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -22,45 +20,65 @@ class CloudinaryAnalytics {
     publicId: this.player.cloudinary.currentPublicId()
   });
 
-  sourceChanged = () => {
+  sourceChanged = (e, { source }) => {
     const metadata = this.getMetadata();
+
     if (metadata.cloudName && metadata.publicId) {
-      this.currentVideMetadata = metadata;
-      this.cloudinaryAnalytics.startManualTracking(metadata, {
+      const isLiveStream = source.resourceConfig().type === 'live';
+      this.currentVideoMetadata = metadata;
+      this.cloudinaryAnalytics.startManualTracking({
+        ...metadata,
+        ...(isLiveStream ? { type: 'live' } : {}),
+      }, {
         ...(isPlainObject(this.analyticsOptions) ? this.analyticsOptions : {}),
         videoPlayerType: 'cloudinary video player',
         videoPlayerVersion: VERSION,
       });
-    } else if (this.currentVideMetadata.cloudName !== metadata.cloudName || this.currentVideMetadata.publicId !== metadata.publicId) {
+    } else if (this.currentVideoMetadata.cloudName !== metadata.cloudName || this.currentVideoMetadata.publicId !== metadata.publicId) {
       this.cloudinaryAnalytics.stopManualTracking();
     }
   };
 
-  dispatchCustomEventOnVideoPlayer = (name, detail = {}) => {
-    const ev = new CustomEvent(`cld-custom-${name}`, {
-      detail,
-    });
-    this.player.videoElement.dispatchEvent(ev);
-  };
+  getCloudinaryVideoPlayerAdapter = () => {
+    const createCldVPEventListener = (callback, eventName) => {
+      this.player.on(eventName, callback);
+      return () => {
+        this.player.off(eventName, callback);
+      };
+    };
 
-  connectCustomEvents = () => {
-    this.player.on(PLAYER_EVENT.PLAY, () => this.dispatchCustomEventOnVideoPlayer('play'));
-    this.player.on(PLAYER_EVENT.PAUSE, () => this.dispatchCustomEventOnVideoPlayer('pause'));
-    this.player.on(PLAYER_EVENT.EMPTIED, () => this.dispatchCustomEventOnVideoPlayer('emptied'));
-    this.player.on(PLAYER_EVENT.LOADED_METADATA, () => {
-      const videoDuration = this.player.videoElement.duration || null;
-      this.dispatchCustomEventOnVideoPlayer('loadedmetadata', {
-        videoDuration,
-      });
-    });
+    return {
+      onCanPlay: (callback) => createCldVPEventListener('canplay', callback),
+      onCanPlayThrough: (callback) => createCldVPEventListener('canplaythrough', callback),
+      onComplete: (callback) => createCldVPEventListener('complete', callback),
+      onDurationChange: (callback) => createCldVPEventListener('durationchange', callback),
+      onEmptied: (callback) => createCldVPEventListener('emptied', callback),
+      onEnded: (callback) => createCldVPEventListener('ended', callback),
+      onError: (callback) => createCldVPEventListener('error', callback),
+      onLoadedData: (callback) => createCldVPEventListener('loadeddata', callback),
+      onLoadedMetadata: (callback) => createCldVPEventListener('loadedmetadata', callback),
+      onLoadStart: (callback) => createCldVPEventListener('loadstart', callback),
+      onPause: (callback) => createCldVPEventListener('pause', callback),
+      onPlay: (callback) => createCldVPEventListener('play', callback),
+      onPlaying: (callback) => createCldVPEventListener('playing', callback),
+      onProgress: (callback) => createCldVPEventListener('progress', callback),
+      onRateChange: (callback) => createCldVPEventListener('ratechange', callback),
+      onSeeked: (callback) => createCldVPEventListener('seeked', callback),
+      onSeeking: (callback) => createCldVPEventListener('seeking', callback),
+      onStalled: (callback) => createCldVPEventListener('stalled', callback),
+      onSuspend: (callback) => createCldVPEventListener('suspend', callback),
+      onTimeUpdate: (callback) => createCldVPEventListener('timeupdate', callback),
+      onVolumeChange: (callback) => createCldVPEventListener('volumechange', callback),
+      onWaiting: (callback) => createCldVPEventListener('waiting', callback),
+
+      getCurrentSrc: () => this.player.videoElement.currentSrc,
+      getCurrentTime: () => this.player.currentTime(),
+      getReadyState: () => this.player.videoElement.readyState,
+      getDuration: () => this.player.duration(),
+    };
   };
 
   init() {
-    // only for iOS there is problem with reporting events because videojs re-triggers events and stops native ones
-    if (this.shouldUseCustomEvents) {
-      this.connectCustomEvents();
-    }
-
path: src/plugins/cloudinary-analytics/index.js
user: login: kalifyaniv-cloudinary
body: why did we remove this? 
created_at: 2025-05-12T11:02:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -22,45 +20,65 @@ class CloudinaryAnalytics {
     publicId: this.player.cloudinary.currentPublicId()
   });
 
-  sourceChanged = () => {
+  sourceChanged = (e, { source }) => {
     const metadata = this.getMetadata();
+
     if (metadata.cloudName && metadata.publicId) {
-      this.currentVideMetadata = metadata;
-      this.cloudinaryAnalytics.startManualTracking(metadata, {
+      const isLiveStream = source.resourceConfig().type === 'live';
+      this.currentVideoMetadata = metadata;
+      this.cloudinaryAnalytics.startManualTracking({
+        ...metadata,
+        ...(isLiveStream ? { type: 'live' } : {}),
+      }, {
         ...(isPlainObject(this.analyticsOptions) ? this.analyticsOptions : {}),
         videoPlayerType: 'cloudinary video player',
         videoPlayerVersion: VERSION,
       });
-    } else if (this.currentVideMetadata.cloudName !== metadata.cloudName || this.currentVideMetadata.publicId !== metadata.publicId) {
+    } else if (this.currentVideoMetadata.cloudName !== metadata.cloudName || this.currentVideoMetadata.publicId !== metadata.publicId) {
       this.cloudinaryAnalytics.stopManualTracking();
     }
   };
 
-  dispatchCustomEventOnVideoPlayer = (name, detail = {}) => {
-    const ev = new CustomEvent(`cld-custom-${name}`, {
-      detail,
-    });
-    this.player.videoElement.dispatchEvent(ev);
-  };
+  getCloudinaryVideoPlayerAdapter = () => {
+    const createCldVPEventListener = (callback, eventName) => {
+      this.player.on(eventName, callback);
+      return () => {
+        this.player.off(eventName, callback);
+      };
+    };
 
-  connectCustomEvents = () => {
-    this.player.on(PLAYER_EVENT.PLAY, () => this.dispatchCustomEventOnVideoPlayer('play'));
-    this.player.on(PLAYER_EVENT.PAUSE, () => this.dispatchCustomEventOnVideoPlayer('pause'));
-    this.player.on(PLAYER_EVENT.EMPTIED, () => this.dispatchCustomEventOnVideoPlayer('emptied'));
-    this.player.on(PLAYER_EVENT.LOADED_METADATA, () => {
-      const videoDuration = this.player.videoElement.duration || null;
-      this.dispatchCustomEventOnVideoPlayer('loadedmetadata', {
-        videoDuration,
-      });
-    });
+    return {
+      onCanPlay: (callback) => createCldVPEventListener('canplay', callback),
+      onCanPlayThrough: (callback) => createCldVPEventListener('canplaythrough', callback),
+      onComplete: (callback) => createCldVPEventListener('complete', callback),
+      onDurationChange: (callback) => createCldVPEventListener('durationchange', callback),
+      onEmptied: (callback) => createCldVPEventListener('emptied', callback),
+      onEnded: (callback) => createCldVPEventListener('ended', callback),
+      onError: (callback) => createCldVPEventListener('error', callback),
+      onLoadedData: (callback) => createCldVPEventListener('loadeddata', callback),
+      onLoadedMetadata: (callback) => createCldVPEventListener('loadedmetadata', callback),
+      onLoadStart: (callback) => createCldVPEventListener('loadstart', callback),
+      onPause: (callback) => createCldVPEventListener('pause', callback),
+      onPlay: (callback) => createCldVPEventListener('play', callback),
+      onPlaying: (callback) => createCldVPEventListener('playing', callback),
+      onProgress: (callback) => createCldVPEventListener('progress', callback),
+      onRateChange: (callback) => createCldVPEventListener('ratechange', callback),
+      onSeeked: (callback) => createCldVPEventListener('seeked', callback),
+      onSeeking: (callback) => createCldVPEventListener('seeking', callback),
+      onStalled: (callback) => createCldVPEventListener('stalled', callback),
+      onSuspend: (callback) => createCldVPEventListener('suspend', callback),
+      onTimeUpdate: (callback) => createCldVPEventListener('timeupdate', callback),
+      onVolumeChange: (callback) => createCldVPEventListener('volumechange', callback),
+      onWaiting: (callback) => createCldVPEventListener('waiting', callback),
+
+      getCurrentSrc: () => this.player.videoElement.currentSrc,
+      getCurrentTime: () => this.player.currentTime(),
+      getReadyState: () => this.player.videoElement.readyState,
+      getDuration: () => this.player.duration(),
+    };
path: src/plugins/cloudinary-analytics/index.js
user: login: jakub-roch
body: for now - no, I just created mapping for all events, it doesn't mean we are listening for all of them, it just means that we can start listening whenever we want to ( thats schema of required props as player adapter )
created_at: 2025-05-12T12:59:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -22,45 +20,65 @@ class CloudinaryAnalytics {
     publicId: this.player.cloudinary.currentPublicId()
   });
 
-  sourceChanged = () => {
+  sourceChanged = (e, { source }) => {
     const metadata = this.getMetadata();
+
     if (metadata.cloudName && metadata.publicId) {
-      this.currentVideMetadata = metadata;
-      this.cloudinaryAnalytics.startManualTracking(metadata, {
+      const isLiveStream = source.resourceConfig().type === 'live';
+      this.currentVideoMetadata = metadata;
+      this.cloudinaryAnalytics.startManualTracking({
+        ...metadata,
+        ...(isLiveStream ? { type: 'live' } : {}),
+      }, {
         ...(isPlainObject(this.analyticsOptions) ? this.analyticsOptions : {}),
         videoPlayerType: 'cloudinary video player',
         videoPlayerVersion: VERSION,
       });
-    } else if (this.currentVideMetadata.cloudName !== metadata.cloudName || this.currentVideMetadata.publicId !== metadata.publicId) {
+    } else if (this.currentVideoMetadata.cloudName !== metadata.cloudName || this.currentVideoMetadata.publicId !== metadata.publicId) {
       this.cloudinaryAnalytics.stopManualTracking();
     }
   };
 
-  dispatchCustomEventOnVideoPlayer = (name, detail = {}) => {
-    const ev = new CustomEvent(`cld-custom-${name}`, {
-      detail,
-    });
-    this.player.videoElement.dispatchEvent(ev);
-  };
+  getCloudinaryVideoPlayerAdapter = () => {
+    const createCldVPEventListener = (callback, eventName) => {
+      this.player.on(eventName, callback);
+      return () => {
+        this.player.off(eventName, callback);
+      };
+    };
 
-  connectCustomEvents = () => {
-    this.player.on(PLAYER_EVENT.PLAY, () => this.dispatchCustomEventOnVideoPlayer('play'));
-    this.player.on(PLAYER_EVENT.PAUSE, () => this.dispatchCustomEventOnVideoPlayer('pause'));
-    this.player.on(PLAYER_EVENT.EMPTIED, () => this.dispatchCustomEventOnVideoPlayer('emptied'));
-    this.player.on(PLAYER_EVENT.LOADED_METADATA, () => {
-      const videoDuration = this.player.videoElement.duration || null;
-      this.dispatchCustomEventOnVideoPlayer('loadedmetadata', {
-        videoDuration,
-      });
-    });
+    return {
+      onCanPlay: (callback) => createCldVPEventListener('canplay', callback),
+      onCanPlayThrough: (callback) => createCldVPEventListener('canplaythrough', callback),
+      onComplete: (callback) => createCldVPEventListener('complete', callback),
+      onDurationChange: (callback) => createCldVPEventListener('durationchange', callback),
+      onEmptied: (callback) => createCldVPEventListener('emptied', callback),
+      onEnded: (callback) => createCldVPEventListener('ended', callback),
+      onError: (callback) => createCldVPEventListener('error', callback),
+      onLoadedData: (callback) => createCldVPEventListener('loadeddata', callback),
+      onLoadedMetadata: (callback) => createCldVPEventListener('loadedmetadata', callback),
+      onLoadStart: (callback) => createCldVPEventListener('loadstart', callback),
+      onPause: (callback) => createCldVPEventListener('pause', callback),
+      onPlay: (callback) => createCldVPEventListener('play', callback),
+      onPlaying: (callback) => createCldVPEventListener('playing', callback),
+      onProgress: (callback) => createCldVPEventListener('progress', callback),
+      onRateChange: (callback) => createCldVPEventListener('ratechange', callback),
+      onSeeked: (callback) => createCldVPEventListener('seeked', callback),
+      onSeeking: (callback) => createCldVPEventListener('seeking', callback),
+      onStalled: (callback) => createCldVPEventListener('stalled', callback),
+      onSuspend: (callback) => createCldVPEventListener('suspend', callback),
+      onTimeUpdate: (callback) => createCldVPEventListener('timeupdate', callback),
+      onVolumeChange: (callback) => createCldVPEventListener('volumechange', callback),
+      onWaiting: (callback) => createCldVPEventListener('waiting', callback),
+
+      getCurrentSrc: () => this.player.videoElement.currentSrc,
+      getCurrentTime: () => this.player.currentTime(),
+      getReadyState: () => this.player.videoElement.readyState,
+      getDuration: () => this.player.duration(),
+    };
   };
 
   init() {
-    // only for iOS there is problem with reporting events because videojs re-triggers events and stops native ones
-    if (this.shouldUseCustomEvents) {
-      this.connectCustomEvents();
-    }
-
path: src/plugins/cloudinary-analytics/index.js
user: login: jakub-roch
body: analytics package was listening for custom event that was triggered by videojs to resolve issue with one event that was actually prevent from bubbling on videojs side for iOS
with new player adapter mechanism its videojs responsibility to trigger events in right moment - so there is no browser-specific or device-specific case as its being handled internally by library
created_at: 2025-05-13T07:30:46Z
author_association: CONTRIBUTOR

PR comments:
user: login: netlify[bot]
created_at: 2025-05-12T09:20:54Z
body: ### <span aria-hidden="true">✅</span> Deploy Preview for *cld-video-player* ready!


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | d028913985a73c577a5ece1da4ff740536a5de8e |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/cld-video-player/deploys/6822fe66e38fa100089a4337 |
|<span aria-hidden="true">😎</span> Deploy Preview | [https://deploy-preview-862--cld-video-player.netlify.app](https://deploy-preview-862--cld-video-player.netlify.app) |
|<span aria-hidden="true">📱</span> Preview on mobile | <details><summary> Toggle QR Code... </summary><br /><br />![QR Code](https://app.netlify.com/qr-code/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RlcGxveS1wcmV2aWV3LTg2Mi0tY2xkLXZpZGVvLXBsYXllci5uZXRsaWZ5LmFwcCJ9.SKsigupKgYL-Ii0ZoPNRdX_9PBF6lRf8rB20iFOVy48)<br /><br />_Use your smartphone camera to open QR code link._</details> |
---
<!-- [cld-video-player Preview](https://deploy-preview-862--cld-video-player.netlify.app) -->
_To edit notification comments on pull requests, go to your [Netlify site configuration](https://app.netlify.com/sites/cld-video-player/configuration/notifications#deploy-webhooks)._
author_association: NONE
user: login: netlify[bot]
created_at: 2025-05-12T09:22:00Z
body: ### <span aria-hidden="true">✅</span> Deploy Preview for *cld-vp-esm-pages* ready!


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | d028913985a73c577a5ece1da4ff740536a5de8e |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/cld-vp-esm-pages/deploys/6822fe66b4ebe900087be022 |
|<span aria-hidden="true">😎</span> Deploy Preview | [https://deploy-preview-862--cld-vp-esm-pages.netlify.app](https://deploy-preview-862--cld-vp-esm-pages.netlify.app) |
|<span aria-hidden="true">📱</span> Preview on mobile | <details><summary> Toggle QR Code... </summary><br /><br />![QR Code](https://app.netlify.com/qr-code/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RlcGxveS1wcmV2aWV3LTg2Mi0tY2xkLXZwLWVzbS1wYWdlcy5uZXRsaWZ5LmFwcCJ9.L9yVQecQ3HsqOFStyhzlLG38QjNNL4i_Q8hwvzHULx0)<br /><br />_Use your smartphone camera to open QR code link._</details> |
---
<!-- [cld-vp-esm-pages Preview](https://deploy-preview-862--cld-vp-esm-pages.netlify.app) -->
_To edit notification comments on pull requests, go to your [Netlify site configuration](https://app.netlify.com/sites/cld-vp-esm-pages/configuration/notifications#deploy-webhooks)._
author_association: NONE