diff --git a/lib/player.js b/lib/player.js index a827bbae3cf..eab5fb964c0 100644 --- a/lib/player.js +++ b/lib/player.js @@ -443,6 +443,56 @@ goog.requireType('shaka.media.PresentationTimeline'); */ +/** + * @event shaka.Player.Started + * @description Fires when the content starts playing. + * Only for VoD. + * @property {string} type + * 'started' + * @exportDoc + */ + + +/** + * @event shaka.Player.FirstQuartile + * @description Fires when the playhead crosses first quartile. + * Only for VoD. + * @property {string} type + * 'firstquartile' + * @exportDoc + */ + + +/** + * @event shaka.Player.Midpoint + * @description Fires when the content playhead crosses midpoint. + * Only for VoD. + * @property {string} type + * 'midpoint' + * @exportDoc + */ + + +/** + * @event shaka.Player.ThirdQuartile + * @description Fires when the content playhead crosses third quartile. + * Only for VoD. + * @property {string} type + * 'thirdquartile' + * @exportDoc + */ + + +/** + * @event shaka.Player.Complete + * @description Fires when the content completes playing. + * Only for VoD. + * @property {string} type + * 'complete' + * @exportDoc + */ + + /** * @summary The main player object for Shaka Player. * @@ -600,6 +650,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget { /** @private {!Array.} */ this.externalSrcEqualsThumbnailsStreams_ = []; + /** @private {number} */ + this.completionPercent_ = NaN; + /** @private {?shaka.extern.PlayerConfiguration} */ this.config_ = this.defaultConfig_(); @@ -1218,6 +1271,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget { this.externalSrcEqualsThumbnailsStreams_ = []; + this.completionPercent_ = NaN; + // Make sure that the app knows of the new buffering state. this.updateBufferState_(); } finally { @@ -1989,6 +2044,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget { this.manifest_.serviceDescription)) { const onTimeUpdate = () => this.onTimeUpdate_(); this.loadEventManager_.listen(mediaElement, 'timeupdate', onTimeUpdate); + } else if (!isLive) { + const onVideoProgress = () => this.onVideoProgress_(); + this.loadEventManager_.listen( + mediaElement, 'timeupdate', onVideoProgress); } if (this.adManager_) { @@ -2309,13 +2368,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget { await fullyLoaded; - if (this.isLive() && this.config_.streaming.liveSync) { + const isLive = this.isLive(); + + if (isLive && this.config_.streaming.liveSync) { const onTimeUpdate = () => this.onTimeUpdate_(); this.loadEventManager_.listen(mediaElement, 'timeupdate', onTimeUpdate); + } else if (!isLive) { + const onVideoProgress = () => this.onVideoProgress_(); + this.loadEventManager_.listen( + mediaElement, 'timeupdate', onVideoProgress); } if (this.adManager_) { - const isLive = this.isLive(); this.adManager_.onManifestUpdated(isLive); // There is no good way to detect when the manifest has been updated, // so we use seekRange().end so we can tell when it has been updated. @@ -5524,6 +5588,50 @@ shaka.Player = class extends shaka.util.FakeEventTarget { } } + /** + * Callback for video progress events + * + * @private + */ + onVideoProgress_() { + if (!this.video_) { + return; + } + let hasNewCompletionPercent = false; + const completionRatio = this.video_.currentTime / this.video_.duration; + if (!isNaN(completionRatio)) { + const percent = Math.round(100 * completionRatio); + if (isNaN(this.completionPercent_)) { + this.completionPercent_ = percent; + hasNewCompletionPercent = true; + } else { + const newCompletionPercent = Math.max(this.completionPercent_, percent); + if (this.completionPercent_ != newCompletionPercent) { + this.completionPercent_ = newCompletionPercent; + hasNewCompletionPercent = true; + } + } + } + if (hasNewCompletionPercent) { + let event; + if (this.completionPercent_ == 0) { + event = this.makeEvent_(shaka.util.FakeEvent.EventName.Started); + } else if (this.completionPercent_ == 25) { + event = this.makeEvent_(shaka.util.FakeEvent.EventName.FirstQuartile); + } else if (this.completionPercent_ == 50) { + event = this.makeEvent_(shaka.util.FakeEvent.EventName.Midpoint); + } else if (this.completionPercent_ == 75) { + event = this.makeEvent_(shaka.util.FakeEvent.EventName.ThirdQuartile); + } else if (this.completionPercent_ == 100) { + event = this.makeEvent_(shaka.util.FakeEvent.EventName.Complete); + } + if (event) { + console.log(event); + this.dispatchEvent(event); + } + } + } + /** * Callback from Playhead. * diff --git a/lib/util/fake_event.js b/lib/util/fake_event.js index a051d9dc97f..2c81e5ac5c4 100644 --- a/lib/util/fake_event.js +++ b/lib/util/fake_event.js @@ -156,6 +156,7 @@ shaka.util.FakeEvent.EventName = { AbrStatusChanged: 'abrstatuschanged', Adaptation: 'adaptation', Buffering: 'buffering', + Complete: 'complete', DownloadFailed: 'downloadfailed', DownloadHeadersReceived: 'downloadheadersreceived', DrmSessionUpdate: 'drmsessionupdate', @@ -163,6 +164,7 @@ shaka.util.FakeEvent.EventName = { Prft: 'prft', Error: 'error', ExpirationUpdated: 'expirationupdated', + FirstQuartile: 'firstquartile', GapJumped: 'gapjumped', KeyStatusChanged: 'keystatuschanged', Loaded: 'loaded', @@ -171,15 +173,18 @@ shaka.util.FakeEvent.EventName = { ManifestUpdated: 'manifestupdated', MediaQualityChanged: 'mediaqualitychanged', Metadata: 'metadata', + Midpoint: 'midpoint', OnStateChange: 'onstatechange', RateChange: 'ratechange', SegmentAppended: 'segmentappended', SessionDataEvent: 'sessiondata', StallDetected: 'stalldetected', + Started: 'started', StateChanged: 'statechanged', Streaming: 'streaming', TextChanged: 'textchanged', TextTrackVisibility: 'texttrackvisibility', + ThirdQuartile: 'thirdquartile', TimelineRegionAdded: 'timelineregionadded', TimelineRegionEnter: 'timelineregionenter', TimelineRegionExit: 'timelineregionexit',