diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 537e390..19ea031 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,8 +9,8 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: - registry-url: 'https://registry.npmjs.org' + registry-url: "https://registry.npmjs.org" - run: npm install --no-save - - run: npm publish --access=public + - run: npm run build && npm publish --access=public env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} \ No newline at end of file + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f86e57..95a5681 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,38 +1,55 @@ # Changelog + All changes to this project will be documented in this file. +## [2.0.0] - 2024-07-31 + +- Switch to api.video analytics v2.0 + ## [1.0.13] - 2022-11-08 + - Fix "seek" event "from" value when seek is the first event - + ## [1.0.12] - 2022-02-21 + - Disallow arrow functions in webpack configuration - + ## [1.0.11] - 2022-01-01 + - Pause pings if the current src is not an api.video one ## [1.0.10] - 2021-09-20 + - Bump npm player-analytics dependency version - + ## [1.0.9] - 2021-07-06 + - Bump npm player-analytics dependency version ## [1.0.8] - 2021-06-30 -- Add dist/* + +- Add dist/\* ## [1.0.7] - 2021-06-30 + - Bump npm player-analytics dependency version ## [1.0.4] - 2021-04-21 + - Bump @api.video/player-analytics - + ## [1.0.3] - 2021-04-21 + - Add some more videojs events - + ## [1.0.2] - 2021-04-20 + - Relay all listened events ## [1.0.1] - 2021-03-24 + - Bump npm dependencies version - + ## [1.0.0] - 2021-03-24 + - First release diff --git a/index.ts b/index.ts index 9ee5751..4c88395 100644 --- a/index.ts +++ b/index.ts @@ -1,230 +1,184 @@ -import { isWithCustomOptions, isWithMediaUrl, PlayerAnalytics, PlayerAnalyticsOptions } from '@api.video/player-analytics'; -import videojs, { VideoJsPlayer } from 'video.js'; - -const Plugin = videojs.getPlugin('plugin'); - -declare module 'video.js' { - // this tells the type system that the VideoJsPlayer object has a method seekButtons - export interface VideoJsPlayer { - apiVideoAnalytics(options?: VideoJsApiVideoAnalyticsOptions): void; - } +import { + isWithCustomOptions, + isWithMediaUrl, + PlayerAnalytics, + PlayerAnalyticsOptions, +} from "@api.video/player-analytics"; +import videojs, { VideoJsPlayer } from "video.js"; + +const Plugin = videojs.getPlugin("plugin"); + +declare module "video.js" { + // this tells the type system that the VideoJsPlayer object has a method seekButtons + export interface VideoJsPlayer { + apiVideoAnalytics(options?: VideoJsApiVideoAnalyticsOptions): void; + } } export class VideoJsApiVideoAnalytics extends Plugin { - private lastPlayingTime: number | undefined = 0; - private beforeLastPlayingTime: number | undefined = 0; - private wasSeeking: boolean = false; - private isFirstPlay = true; - private options: VideoJsApiVideoAnalyticsOptions; - private skipNextSeek = false; - private isFirstInit = true; - private lastSegmentBandwidth = 0; - private playerAnalytics!: PlayerAnalytics; - private passthrough: boolean = false; - private lastMediaUrl?: string = undefined; - - constructor(player: VideoJsPlayer, options: VideoJsApiVideoAnalyticsOptions) { - super(player); - - this.options = options || {}; - if (!isWithCustomOptions(this.options) && !isWithMediaUrl(this.options)) { - player.on('loadstart', (_) => { - const src = player.src(); - if (this.isApiVideoMediaUrl(src)) { - this.passthrough = false; - if(this.lastMediaUrl !== src) { - (this.options as any).mediaUrl = src; - this.setOptions(this.options); - this.lastMediaUrl = src; - } - } else { - this.passthrough = true; - } - }); - } else { + private options: VideoJsApiVideoAnalyticsOptions; + private isFirstInit = true; + private lastSegmentBandwidth = 0; + private playerAnalytics!: PlayerAnalytics; + private passthrough: boolean = false; + private lastMediaUrl?: string = undefined; + + constructor(player: VideoJsPlayer, options: VideoJsApiVideoAnalyticsOptions) { + super(player); + + this.options = options || {}; + if (!isWithCustomOptions(this.options) && !isWithMediaUrl(this.options)) { + player.on("loadstart", (_) => { + const src = player.src(); + if (this.isApiVideoMediaUrl(src)) { + this.passthrough = false; + if (this.lastMediaUrl !== src) { + (this.options as any).mediaUrl = src; this.setOptions(this.options); + this.lastMediaUrl = src; + + const videoElt = this.player.el().querySelector("video"); + if (videoElt) { + this.playerAnalytics.ovbserveMedia(videoElt); + } else { + console.error("No video element found in the player"); + } + } + } else { + this.passthrough = true; } + }); + } else { + this.setOptions(this.options); + } + } + + public setOptions(options: VideoJsApiVideoAnalyticsOptions) { + this.options = options; + + this.playerAnalytics = new PlayerAnalytics({ + ...options, + }); + + this.player.ready(() => { + if (this.isFirstInit) { + events.forEach((eventName) => + this.player.on(eventName, (event: any) => + this.handleEvent(eventName, event) + ) + ); + } + + this.isFirstInit = false; + }); + } + + private isApiVideoMediaUrl(mediaUrl: string): boolean { + try { + PlayerAnalytics.parseMediaUrl(mediaUrl); + return true; + } catch (e: any) { + return false; } + } - public setOptions(options: VideoJsApiVideoAnalyticsOptions) { - this.options = options; - this.isFirstPlay = true; + private initSegmentsWatcher() { + const tracks = this.player.textTracks(); - if (this.playerAnalytics) { - this.playerAnalytics.destroy(); - } + if (!tracks) { + return; + } - this.playerAnalytics = new PlayerAnalytics({ - ...options - }); + // tslint:disable-next-line + for (let i = 0; i < tracks.length; i++) { + if (tracks[i].label === "segment-metadata") { + const segmentMetadataTrack = tracks[i] as any; - this.player.ready(() => { - if (this.isFirstInit) { - events.forEach(eventName => this.player.on(eventName, (event: any) => this.handleEvent(eventName, event))); - window.addEventListener('beforeunload', (e) => this.playerAnalytics.destroy()); - } + segmentMetadataTrack.on("cuechange", () => { + const activeCue = segmentMetadataTrack.activeCues[0]; - if (!!options.sequence) { - this.skipNextSeek = true; - } + if (!activeCue?.value) { + return; + } - this.playerAnalytics.updateTime(options.sequence?.start || 0); - this.playerAnalytics.ready(); + this.handleEvent("segmentchange", activeCue.value); - this.isFirstInit = false; + if (this.lastSegmentBandwidth !== activeCue.value.bandwidth) { + this.lastSegmentBandwidth = activeCue.value.bandwidth; + this.handleEvent("qualitychange", activeCue.value.resolution); + } }); + return; + } } + } - private isApiVideoMediaUrl(mediaUrl: string): boolean { - try { - PlayerAnalytics.parseMediaUrl(mediaUrl); - return true; - } catch (e: any) { - return false; - } + private handleEvent(eventName: string, event: any) { + if (this.passthrough) { + return; } - private initSegmentsWatcher() { - const tracks = this.player.textTracks(); - - if (!tracks) { - return; - } - - // tslint:disable-next-line - for (let i = 0; i < tracks.length; i++) { - if (tracks[i].label === 'segment-metadata') { - const segmentMetadataTrack = tracks[i] as any; - - segmentMetadataTrack.on('cuechange', () => { - const activeCue = segmentMetadataTrack.activeCues[0]; - - if (!activeCue?.value) { - return; - } - - this.handleEvent('segmentchange', activeCue.value); - - if (this.lastSegmentBandwidth !== activeCue.value.bandwidth) { - this.lastSegmentBandwidth = activeCue.value.bandwidth; - this.handleEvent('qualitychange', activeCue.value.resolution); - } - - }); - return; - } - } + if (this.options.onEvent && eventName === "loadedmetadata") { + this.options.onEvent({ type: "ready" }); + this.initSegmentsWatcher(); } - private handleEvent(eventName: string, event: any) { - if (this.passthrough) { - return; - } - - if (this.options.onEvent && eventName === 'loadedmetadata') { - this.options.onEvent({ type: 'ready' }); - this.initSegmentsWatcher(); - } - - if (eventName === 'timeupdate') { - this.playerAnalytics.updateTime(this.player.currentTime()); - if (!this.player.paused()) { - this.beforeLastPlayingTime = this.lastPlayingTime; - this.lastPlayingTime = this.player.currentTime(); - } - } - - if (eventName === 'dispose') { - this.playerAnalytics.destroy(); - } - - if (eventName === 'play') { - if (this.wasSeeking) { - if (!this.skipNextSeek) { - const endSeeking = this.player.currentTime(); - const startSeeking = this.lastPlayingTime === endSeeking ? this.beforeLastPlayingTime : this.lastPlayingTime; - this.playerAnalytics.seek(startSeeking as number, endSeeking); - } else { - this.skipNextSeek = false; - } - this.wasSeeking = false; - } - - if (this.isFirstPlay) { - this.playerAnalytics.play(); - this.isFirstPlay = false; - } else { - this.playerAnalytics.resume(); - } - } - - if (eventName === 'pause') { - if (this.player.currentTime() !== this.player.duration()) { - this.playerAnalytics.pause(); - } - } - - if (eventName === 'seeking') { - this.wasSeeking = true; - } - - if (eventName === 'ended') { - this.playerAnalytics.end(); - } - - if (this.options.onEvent) { - this.options.onEvent({ - type: eventName, - ...(eventName === 'timeupdate' ? { currentTime: this.player.currentTime() } : {}), - ...(eventName === 'volumechange' ? { volume: this.player.volume() } : {}), - ...(eventName === 'segmentchange' ? { segment: event } : {}), - ...(eventName === 'qualitychange' ? { resolution: event } : {}), - }); - } + if (this.options.onEvent) { + this.options.onEvent({ + type: eventName, + ...(eventName === "timeupdate" + ? { currentTime: this.player.currentTime() } + : {}), + ...(eventName === "volumechange" + ? { volume: this.player.volume() } + : {}), + ...(eventName === "segmentchange" ? { segment: event } : {}), + ...(eventName === "qualitychange" ? { resolution: event } : {}), + }); } + } } - const events = [ - 'controlsdisabled', - 'controlsenabled', - 'dispose', - 'durationchange', - 'ended', - 'enterFullWindow', - 'enterpictureinpicture', - 'error', - 'exitFullWindow', - 'firstplay', - 'fullscreenchange', - 'leavepictureinpicture', - 'loadedmetadata', - 'loadstart', - 'pause', - 'play', - 'playerreset', - 'playerresize', - 'playing', - 'posterchange', - 'ratechange', - 'ready', - 'resize', - 'seeked', - 'seeking', - 'textdata', - 'timeupdate', - 'useractive', - 'userinactive', - 'volumechange', - 'waiting', + "controlsdisabled", + "controlsenabled", + "dispose", + "durationchange", + "ended", + "enterFullWindow", + "enterpictureinpicture", + "error", + "exitFullWindow", + "firstplay", + "fullscreenchange", + "leavepictureinpicture", + "loadedmetadata", + "loadstart", + "pause", + "play", + "playerreset", + "playerresize", + "playing", + "posterchange", + "ratechange", + "ready", + "resize", + "seeked", + "seeking", + "textdata", + "timeupdate", + "useractive", + "userinactive", + "volumechange", + "waiting", ]; export type VideoJsApiVideoAnalyticsOptions = PlayerAnalyticsOptions & { - onEvent?: (event: any) => void; -} + onEvent?: (event: any) => void; +}; type VideoQuality = { - width: number; - height: number; - bandwidth: number; -} \ No newline at end of file + width: number; + height: number; + bandwidth: number; +}; diff --git a/package-lock.json b/package-lock.json index 69f5b4c..314ebd7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@api.video/videojs-player-analytics", - "version": "1.0.13", + "version": "2.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@api.video/videojs-player-analytics", - "version": "1.0.13", + "version": "2.0.0", "license": "MIT", "dependencies": { - "@api.video/player-analytics": "^1.0.9", + "@api.video/player-analytics": "^2.0.0", "@types/video.js": "^7.3.15", "core-js": "^3.10.0", "url-polyfill": "^1.1.12" @@ -62,12 +62,11 @@ } }, "node_modules/@api.video/player-analytics": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@api.video/player-analytics/-/player-analytics-1.0.9.tgz", - "integrity": "sha512-thNwYlFxxQtNDgZbNMyMZci+nw+YGk2YJlbhR32q7LHKH/L0ZmjSORqvb04X0hY6n21A6uAATALYmxbQ1FXY0w==", + "version": "2.0.0", + "resolved": "file:../player-analytics/api.video-player-analytics-2.0.0.tgz", + "integrity": "sha512-vTjyQvEoMJD7GRNcjrRxPilyIqiqWWFlZIZ/1vQK679568+/+U7hDa4C1HSXAw8VbcGdlvtKUVB1zha3qr/8cQ==", "dependencies": { "core-js": "^3.8.3", - "md5": "^2.3.0", "url-polyfill": "^1.1.12", "whatwg-fetch": "^3.6.2" } @@ -1967,14 +1966,6 @@ "node": ">=10" } }, - "node_modules/charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=", - "engines": { - "node": "*" - } - }, "node_modules/chrome-trace-event": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", @@ -2193,14 +2184,6 @@ "semver": "bin/semver" } }, - "node_modules/crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=", - "engines": { - "node": "*" - } - }, "node_modules/cssom": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", @@ -3382,7 +3365,8 @@ "node_modules/is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true }, "node_modules/is-ci": { "version": "2.0.0", @@ -4668,16 +4652,6 @@ "node": ">=0.10.0" } }, - "node_modules/md5": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", - "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", - "dependencies": { - "charenc": "0.0.2", - "crypt": "0.0.2", - "is-buffer": "~1.1.6" - } - }, "node_modules/memory-fs": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", @@ -7621,9 +7595,9 @@ } }, "node_modules/whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==" }, "node_modules/whatwg-mimetype": { "version": "2.3.0", @@ -7812,12 +7786,10 @@ }, "dependencies": { "@api.video/player-analytics": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@api.video/player-analytics/-/player-analytics-1.0.9.tgz", - "integrity": "sha512-thNwYlFxxQtNDgZbNMyMZci+nw+YGk2YJlbhR32q7LHKH/L0ZmjSORqvb04X0hY6n21A6uAATALYmxbQ1FXY0w==", + "version": "2.0.0", + "integrity": "sha512-vTjyQvEoMJD7GRNcjrRxPilyIqiqWWFlZIZ/1vQK679568+/+U7hDa4C1HSXAw8VbcGdlvtKUVB1zha3qr/8cQ==", "requires": { "core-js": "^3.8.3", - "md5": "^2.3.0", "url-polyfill": "^1.1.12", "whatwg-fetch": "^3.6.2" } @@ -9420,11 +9392,6 @@ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true }, - "charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" - }, "chrome-trace-event": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", @@ -9608,11 +9575,6 @@ } } }, - "crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" - }, "cssom": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", @@ -10541,7 +10503,8 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true }, "is-ci": { "version": "2.0.0", @@ -11542,16 +11505,6 @@ "object-visit": "^1.0.0" } }, - "md5": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", - "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", - "requires": { - "charenc": "0.0.2", - "crypt": "0.0.2", - "is-buffer": "~1.1.6" - } - }, "memory-fs": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", @@ -13837,9 +13790,9 @@ } }, "whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==" }, "whatwg-mimetype": { "version": "2.3.0", diff --git a/package.json b/package.json index 11e10f8..5f3822d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@api.video/videojs-player-analytics", - "version": "1.0.13", + "version": "2.0.0", "description": "api.video player analytics plugin for videojs", "repository": { "type": "git", @@ -20,8 +20,8 @@ "typings": "./dist/index.d.ts", "scripts": { "tslint": "tslint --project .", - "build": "npm run tslint && webpack --mode production", - "test": "npm run tslint && jest", + "build": "webpack --mode production", + "test": "jest", "prepublishOnly": "npm run build" }, "devDependencies": { @@ -43,7 +43,7 @@ "webpack-cli": "^4.9.2" }, "dependencies": { - "@api.video/player-analytics": "^1.0.9", + "@api.video/player-analytics": "^2.0.0", "@types/video.js": "^7.3.15", "core-js": "^3.10.0", "url-polyfill": "^1.1.12" diff --git a/samples/simple-javascript/index.html b/samples/simple-javascript/index.html index b282404..fd2a592 100644 --- a/samples/simple-javascript/index.html +++ b/samples/simple-javascript/index.html @@ -1,23 +1,29 @@ - - + - - - - + + -