feat: Pause mediaContentTimeSpent calculation on ad breaks#777
feat: Pause mediaContentTimeSpent calculation on ad breaks#777mmustafa-tse wants to merge 1 commit intomParticle:developmentfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds an optional feature to pause mediaContentTimeSpent calculation during ad breaks. When the new pauseOnAdBreak flag is set to true on MediaSession initialization, the time spent in ad breaks will be excluded from content time tracking. This addresses the issue where developers previously had to manually manage content time tracking during ad breaks, which could lead to overstated media content time values.
- Adds
pauseOnAdBreakboolean parameter to MediaSession constructor (defaults tofalse) - Implements ad break time tracking using timestamps on AdBreak start/end
- Modifies
mediaContentTimeSpent()calculation to subtract accumulated ad break time when the flag is enabled
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 10 comments.
| File | Description |
|---|---|
| src/session.ts | Adds pauseOnAdBreak parameter, implements ad break time tracking logic, and modifies mediaContentTimeSpent calculation |
| src/types.ts | Extends AdBreak type with optional timestamp fields for tracking ad break duration |
| test/session.test.ts | Adds tests for pauseOnAdBreak feature (both enabled/disabled cases) and updates existing tests with new constructor parameter |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Another 100ms delay added after logPause is triggered to account for time spent on media session (total = +200ms). | ||
| await new Promise(f => setTimeout(f, 100)); | ||
| customSession.logAdBreakEnd(options); | ||
| // Another 100ms delay added after logPause is triggered to account for time spent on media session (total = +200ms). |
There was a problem hiding this comment.
The comment mentions "logPause is triggered" but logPause is not actually called in this test. This delay occurs after logAdBreakEnd, before logMediaSessionEnd.
| // Another 100ms delay added after logPause is triggered to account for time spent on media session (total = +200ms). | |
| // Another 100ms delay added after logAdBreakEnd to account for time spent on media session (total = +200ms). |
| // Another 100ms delay added after logPause is triggered to account for time spent on media session (total = +200ms). | ||
| await new Promise(f => setTimeout(f, 100)); | ||
| customSession.logAdBreakEnd(options); | ||
| // Another 100ms delay added after logPause is triggered to account for time spent on media session (total = +200ms). |
There was a problem hiding this comment.
The comment mentions "logPause is triggered" but logPause is not actually called in this test. This delay occurs after logAdBreakEnd, before logMediaSessionEnd.
| // Another 100ms delay added after logPause is triggered to account for time spent on media session (total = +200ms). | |
| // Another 100ms delay added after logAdBreakEnd to account for time spent on media session (total = +200ms). |
| it('should pause mediaContentTimeSpent when pauseOnAdBreak is true', async () => { | ||
| const mediaSessionAttributes = { | ||
| session_name: 'amazing-current-session', | ||
| session_start_time: 'right-now', | ||
| custom_session_value: 'this-is-custom', | ||
| }; | ||
|
|
||
| const customSession: MediaSession = new MediaSession( | ||
| mp, | ||
| song.contentId, | ||
| song.title, | ||
| song.duration, | ||
| song.contentType, | ||
| song.streamType, | ||
| false, | ||
| true, | ||
| true, | ||
| mediaSessionAttributes, | ||
| ); | ||
|
|
||
| const bond = sinon.spy(mp, 'logBaseEvent'); | ||
|
|
||
| customSession.logMediaSessionStart(); | ||
|
|
||
| const options = { | ||
| customAttributes: { | ||
| content_rating: 'epic', | ||
| }, | ||
| currentPlayheadPosition: 0, | ||
| }; | ||
|
|
||
| const adBreak: AdBreak = { | ||
| id: '08123410', | ||
| title: 'mid-roll', | ||
| duration: 10000, | ||
| }; | ||
|
|
||
| // logPlay is triggered to start media content time tracking. | ||
| customSession.logPlay(options); | ||
| // 100ms delay added to account for the time spent on media content. | ||
| await new Promise(f => setTimeout(f, 100)); | ||
| customSession.logAdBreakStart(adBreak); | ||
| // Another 100ms delay added after logPause is triggered to account for time spent on media session (total = +200ms). | ||
| await new Promise(f => setTimeout(f, 100)); | ||
| customSession.logAdBreakEnd(options); | ||
| // Another 100ms delay added after logPause is triggered to account for time spent on media session (total = +200ms). | ||
| await new Promise(f => setTimeout(f, 100)); | ||
| customSession.logMediaSessionEnd(options); | ||
|
|
||
| // the 6th event in bond.args is the Media Session Summary which contains the mediaContentTimeSpent and mediaTimeSpent. | ||
| const mpMediaContentTimeSpent = | ||
| bond.args[5][0].options.customAttributes | ||
| .media_content_time_spent; | ||
|
|
||
| // the mediaContentTimeSpent varies in value each test run by a millisecond or two (i,e value is could be 100ms, 101ms, 102ms) | ||
| // and we can't determine the exact value, hence the greaterThanOrEqual and lessThanOrEqual tests. | ||
| expect(mpMediaContentTimeSpent).to.greaterThanOrEqual(200); | ||
| expect(mpMediaContentTimeSpent).to.lessThanOrEqual(300); | ||
| }); |
There was a problem hiding this comment.
The test for pauseOnAdBreak feature doesn't include scenarios where logPause is called during or after an ad break. This is important to test because the storedPlaybackTime calculation in logPause doesn't account for ad break time when pauseOnAdBreak is enabled, which could lead to incorrect mediaContentTimeSpent values. Consider adding test cases that call logPlay, logAdBreakStart, logAdBreakEnd, logPause, and then verify mediaContentTimeSpent excludes ad break time.
| */ | ||
| adBreakStartTimestamp?: number; | ||
| /** | ||
| * Timestamp for AdBreak end playback | ||
| */ | ||
| adBreakEndTimestamp?: number; |
There was a problem hiding this comment.
The adBreakStartTimestamp and adBreakEndTimestamp fields are added to the AdBreak type as optional properties, but they are being used internally for tracking purposes rather than being set by the API consumer. Consider whether these implementation details should be exposed in the public AdBreak type, or if they should be tracked separately in the MediaSession class. This could prevent API consumers from inadvertently setting these values when creating AdBreak objects.
| */ | |
| adBreakStartTimestamp?: number; | |
| /** | |
| * Timestamp for AdBreak end playback | |
| */ | |
| adBreakEndTimestamp?: number; | |
| * (internal use only) | |
| */ | |
| // adBreakStartTimestamp?: number; | |
| /** | |
| * Timestamp for AdBreak end playback | |
| * (internal use only) | |
| // adBreakEndTimestamp?: number; |
| bond.args[5][0].options.customAttributes | ||
| .media_content_time_spent; | ||
|
|
||
| // the mediaContentTimeSpent varies in value each test run by a millisecond or two (i,e value is could be 100ms, 101ms, 102ms) |
There was a problem hiding this comment.
Typo: "i,e" should be "i.e." (the abbreviation for "id est" meaning "that is").
| // the mediaContentTimeSpent varies in value each test run by a millisecond or two (i,e value is could be 100ms, 101ms, 102ms) | |
| // the mediaContentTimeSpent varies in value each test run by a millisecond or two (i.e. value could be 100ms, 101ms, 102ms) |
| bond.args[5][0].options.customAttributes | ||
| .media_content_time_spent; | ||
|
|
||
| // the mediaContentTimeSpent varies in value each test run by a millisecond or two (i,e value is could be 100ms, 101ms, 102ms) |
There was a problem hiding this comment.
Typo: "i,e" should be "i.e." (the abbreviation for "id est" meaning "that is").
| // the mediaContentTimeSpent varies in value each test run by a millisecond or two (i,e value is could be 100ms, 101ms, 102ms) | |
| // the mediaContentTimeSpent varies in value each test run by a millisecond or two (i.e. value could be 100ms, 101ms, 102ms) |
| mediaAdBreakTimeSpent) | ||
| ); | ||
| } else { | ||
| return this.storedPlaybackTime; |
There was a problem hiding this comment.
When pauseOnAdBreak is enabled and playback is paused (currentPlaybackStartTimestamp is undefined), mediaContentTimeSpent should still exclude ad break time by subtracting mediaAdBreakTimeSpent from storedPlaybackTime. Currently, only the active playback branch (line 109-114) subtracts ad break time, but the paused branch returns storedPlaybackTime without this adjustment. This causes mediaContentTimeSpent to incorrectly include ad break duration when calculated after a pause.
| return this.storedPlaybackTime; | |
| return this.storedPlaybackTime - mediaAdBreakTimeSpent; |
| // Another 100ms delay added after logPause is triggered to account for time spent on media session (total = +200ms). | ||
| await new Promise(f => setTimeout(f, 100)); | ||
| customSession.logAdBreakEnd(options); | ||
| // Another 100ms delay added after logPause is triggered to account for time spent on media session (total = +200ms). |
There was a problem hiding this comment.
The comment mentions "logPause is triggered" but logPause is not actually called in this test. The comment should accurately describe that this delay occurs after logAdBreakStart, before logAdBreakEnd. The same issue exists in the comment at line 232 (after logAdBreakEnd, before logMediaSessionEnd).
| // Another 100ms delay added after logPause is triggered to account for time spent on media session (total = +200ms). | |
| await new Promise(f => setTimeout(f, 100)); | |
| customSession.logAdBreakEnd(options); | |
| // Another 100ms delay added after logPause is triggered to account for time spent on media session (total = +200ms). | |
| // Another 100ms delay added after logAdBreakStart and before logAdBreakEnd to account for time spent on media session (total = +200ms). | |
| await new Promise(f => setTimeout(f, 100)); | |
| customSession.logAdBreakEnd(options); | |
| // Another 100ms delay added after logAdBreakEnd and before logMediaSessionEnd to account for time spent on media session (total = +300ms). |
| // 100ms delay added to account for the time spent on media content. | ||
| await new Promise(f => setTimeout(f, 100)); | ||
| customSession.logAdBreakStart(adBreak); | ||
| // Another 100ms delay added after logPause is triggered to account for time spent on media session (total = +200ms). |
There was a problem hiding this comment.
The comment mentions "logPause is triggered" but logPause is not actually called in this test. This delay occurs after logAdBreakStart, before logAdBreakEnd.
| // Another 100ms delay added after logPause is triggered to account for time spent on media session (total = +200ms). | |
| // Another 100ms delay added after logAdBreakStart to account for time spent on media session (total = +200ms). |
| * @param streamType A descriptor for the type of stream, i.e. live or on demand | ||
| * @param logPageEvent A flag that toggles sending mParticle Events to Core SDK | ||
| * @param logMediaEvent A flag that toggles sending Media Events to Core SDK | ||
| * @param pauseOnAdBreak A flag that toggles wether to stop calculating mediaContentTimespent when ad break events are logged |
There was a problem hiding this comment.
Typo: "wether" should be "whether"
| * @param pauseOnAdBreak A flag that toggles wether to stop calculating mediaContentTimespent when ad break events are logged | |
| * @param pauseOnAdBreak A flag that toggles whether to stop calculating mediaContentTimespent when ad break events are logged |
Background
What Has Changed
Screenshots/Video
Checklist
Additional Notes
Reference Issue (For employees only. Ignore if you are an outside contributor)