Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
# Changelog #

## v1.4.1 (September 17, 2025) ##

- Tweak: Enhanced Audio Recording support in GoDAM Recorder for Gravity Forms
- Fix: Video player UI bugs and playback quality issues

## v1.4.0 (September 9, 2025) ##

- New: Added integration for LifterLMS
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Tested up to: 6.8.1

Requires PHP: 7.4

Stable tag: 1.4.1
Stable tag: 1.4.0

License: [GPLv2 or later](http://www.gnu.org/licenses/gpl-2.0.html)

Expand Down
231 changes: 200 additions & 31 deletions assets/src/js/godam-player/analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,184 @@ const analytics = Analytics( {
} );
window.analytics = analytics;

// Track which videos have been processed to avoid duplicates
const processedVideos = new Set();

// Track video states for proper heatmap handling
const videoState = new Map(); // videoId -> { hasPlayed: false, sent: false, ranges: [], duration: 0 }

// Function to send page_load for a single video
function sendPageLoadForVideo( videoId ) {
if ( window.analytics && videoId ) {
window.analytics.track( 'page_load', {
type: 1, // Enum: 1 = Page Load
videoIds: [ parseInt( videoId, 10 ) ],
} );
}
}

// Function to setup tracking for a video
function setupTracking( videoElement ) {
const videoId = videoElement.getAttribute( 'data-id' );
if ( ! videoId || videoState.has( videoId ) ) {
return;
}

const player = videojs( videoElement );
videoState.set( videoId, {
hasPlayed: false,
sent: false,
playedObject: null, // Store the played object directly
duration: 0,
} );

const updateCache = () => {
const state = videoState.get( videoId );
if ( ! state ) {
return;
}

try {
// Store the played object directly
state.playedObject = player.played();
state.duration = Number( player.duration() ) || 0;
} catch ( error ) {
// Player might be disposed
}
};

// Track when video starts playing
player.on( 'play', () => {
const state = videoState.get( videoId );
if ( state ) {
state.hasPlayed = true;
}
} );

// Track time updates and cache ranges
player.on( 'timeupdate', () => {
const state = videoState.get( videoId );
if ( ! state ) {
return;
}
if ( player.currentTime() > 0.01 && ! state.hasPlayed ) {
state.hasPlayed = true;
}
updateCache();
} );

player.on( 'pause', updateCache );
player.on( 'ended', () => {
const state = videoState.get( videoId );
if ( state ) {
state.hasPlayed = true;
}
updateCache();
} );

// Send heatmap data when player is disposed
player.on( 'dispose', () => {
flushHeatmap( videoId );
} );
}

// Function to flush heatmap data for a video
function flushHeatmap( videoId ) {
const state = videoState.get( videoId );
if ( ! state || state.sent ) {
return;
}

// Only send if video was actually played and has meaningful data
if ( ! state.hasPlayed || ! state.playedObject || ! state.duration ) {
return;
}

// Do the processing of the played object
const ranges = [];
for ( let i = 0; i < state.playedObject.length; i++ ) {
ranges.push( [ state.playedObject.start( i ), state.playedObject.end( i ) ] );
}

if ( ranges.length === 0 ) {
return;
}

if ( window.analytics ) {
window.analytics.track( 'video_heatmap', {
type: 2, // Enum: 2 = Heatmap
videoId: parseInt( videoId, 10 ),
ranges,
videoLength: state.duration,
} );
}
state.sent = true;
}

// MutationObserver to detect new videos (only after initial page load)
let observerStarted = false;

const observer = new MutationObserver( ( mutations ) => {
// Only process if observer has been started (after initial page load)
if ( ! observerStarted ) {
return;
}

for ( const mutation of mutations ) {
mutation.addedNodes.forEach( ( node ) => {
if ( ! ( node instanceof Element ) ) {
return;
}

// If the node itself is a player
if ( node.matches && node.matches( '.easydam-player.video-js' ) ) {
const videoId = node.getAttribute( 'data-id' );
if ( videoId && ! processedVideos.has( videoId ) ) {
processedVideos.add( videoId );
sendPageLoadForVideo( videoId );
setupTracking( node );
}
}

// Or contains players inside it
const nested = node.querySelectorAll ? node.querySelectorAll( '.easydam-player.video-js' ) : [];
nested.forEach( ( video ) => {
const videoId = video.getAttribute( 'data-id' );
if ( videoId && ! processedVideos.has( videoId ) ) {
processedVideos.add( videoId );
sendPageLoadForVideo( videoId );
setupTracking( video );
}
} );
} );

// Handle removals
mutation.removedNodes.forEach( ( node ) => {
if ( ! ( node instanceof Element ) ) {
return;
}
// Check if removed node is a video
if ( node.matches && node.matches( '.easydam-player.video-js' ) ) {
const videoId = node.getAttribute( 'data-id' );
if ( videoId ) {
flushHeatmap( videoId );
}
}

// Check if removed node contains videos
const nestedVideos = node.querySelectorAll ? node.querySelectorAll( '.easydam-player.video-js' ) : [];
if ( nestedVideos.length > 0 ) {
nestedVideos.forEach( ( video ) => {
const videoId = video.getAttribute( 'data-id' );
if ( videoId ) {
flushHeatmap( videoId );
}
} );
}
} );
}
} );

if ( ! window.pageLoadEventTracked ) {
window.pageLoadEventTracked = true; // Mark as tracked to avoid duplicate execution

Expand All @@ -36,46 +214,37 @@ if ( ! window.pageLoadEventTracked ) {
} );
}

// Mark initial videos as processed and setup tracking
videos.forEach( ( videoElement ) => {
const idAttr = videoElement.getAttribute( 'data-id' );
if ( ! idAttr ) {
return;
}
processedVideos.add( idAttr );
setupTracking( videoElement );
} );

// Initialize video analytics
playerAnalytics();

// NOW start the observer for new videos
observerStarted = true;
observer.observe( document.documentElement, { childList: true, subtree: true } );
} );
}

function playerAnalytics() {
const videos = document.querySelectorAll( '.easydam-player.video-js' );

videos.forEach( ( video ) => {
// read the data-setup attribute.
const player = videojs( video );

window.addEventListener( 'beforeunload', () => {
const played = player.played();
const ranges = [];
const videoLength = player.duration();

// Extract time ranges from the player.played() object
for ( let i = 0; i < played.length; i++ ) {
ranges.push( [ played.start( i ), played.end( i ) ] );
}

// Send the ranges using updateHeatmap
updateHeatmap( ranges, videoLength );
window.addEventListener( 'beforeunload', () => {
// Flush all unsent heatmap data on page exit
videoState.forEach( ( state, videoId ) => {
flushHeatmap( videoId );
} );
} );

async function updateHeatmap( ranges, videoLength ) {
const videoId = video.getAttribute( 'data-id' );
if ( ! videoId || ranges.length === 0 ) {
return; // Skip sending if no valid data
}

if ( window.analytics ) {
window.analytics.track( 'video_heatmap', {
type: 2, // Enum: 2 = Heatmap
videoId: parseInt( videoId, 10 ),
ranges,
videoLength,
} );
}
}
// Per-video initialization (no global listeners here)
videos.forEach( ( video ) => {
videojs( video );
} );
}
4 changes: 2 additions & 2 deletions godam.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Plugin Name: GoDAM
* Plugin URI: https://godam.io
* Description: Seamlessly manage and deliver your media assets directly from the cloud-based media management. Store assets efficiently, stream them via a CDN, and enhance your website's performance and user experience. Featuring adaptive bit rate streaming, adding interactive layers in videos, and taking full advantage of a digital asset management solution within WordPress.
* Version: 1.4.1
* Version: 1.4.0
* Requires at least: 6.5
* Requires PHP: 7.4
* Text Domain: godam
Expand Down Expand Up @@ -43,7 +43,7 @@
/**
* The version of the plugin
*/
define( 'RTGODAM_VERSION', '1.4.1' );
define( 'RTGODAM_VERSION', '1.4.0' );
}

if ( ! defined( 'RTGODAM_API_BASE' ) ) {
Expand Down
2 changes: 1 addition & 1 deletion inc/helpers/custom-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ function rtgodam_is_api_key_valid() {
*
* Note: The files created by uppy webcam, screen capture, and audio plugin are in the same format. So we are checking the filename to determine if it's an audio file.
*
* @since 1.4.1
* @since n.e.x.t
Copy link

Copilot AI Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The @SInCE tag 'n.e.x.t' appears to be a typo. It should be a proper version number like '1.4.0' or 'next' if referring to the next release.

Suggested change
* @since n.e.x.t
* @since next

Copilot uses AI. Check for mistakes.
*
* @param string $filename The name of the file to check.
*
Expand Down
Loading