Feature/63538 voka api
This commit is contained in:
@@ -1,6 +1,16 @@
|
||||
{
|
||||
"data": {
|
||||
"url": "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8",
|
||||
"subtitles": "https://raw.githubusercontent.com/videojs/video.js/c7298d40a4632a6e9dfcd5a2f5cc3bbe92a78744/docs/examples/elephantsdream/captions.ru.vtt"
|
||||
"subtitles": "https://raw.githubusercontent.com/videojs/video.js/c7298d40a4632a6e9dfcd5a2f5cc3bbe92a78744/docs/examples/elephantsdream/captions.ru.vtt",
|
||||
"analytics_v2": {
|
||||
"url": "https://juraldinio.com/analytics/",
|
||||
"interval": 5000,
|
||||
"additional_parameters": {
|
||||
"application_id": "42",
|
||||
"user_id": "12321",
|
||||
"resource_type": "video",
|
||||
"watch_session_id": "100500"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -38,6 +38,6 @@
|
||||
"devDependencies": {
|
||||
"@swc/core": "^1.11.20",
|
||||
"terser": "^5.37.0",
|
||||
"video.js": "^8.21.0"
|
||||
"video.js": "^8.23.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
enum WebOSDRMClientType {
|
||||
PLAYREADY = 'playready',
|
||||
WIDEWINE = 'widevine'
|
||||
WIDEVINE = 'widevine'
|
||||
}
|
||||
|
||||
interface WebOSDRMClient {
|
||||
|
||||
@@ -173,7 +173,7 @@ class WebOSPlayerNativeDRMImpl {
|
||||
new Error(`Player.sendDrmMessage onSuccess: [${resultCode}] DRM message error`)
|
||||
)
|
||||
}
|
||||
} else if (client.type == WebOSDRMClientType.WIDEWINE) {
|
||||
} else if (client.type == WebOSDRMClientType.WIDEVINE) {
|
||||
resolve(result)
|
||||
}
|
||||
},
|
||||
@@ -247,7 +247,7 @@ class WebOSPlayerNativeDRMImpl {
|
||||
message += '</LicenseServerUriOverride>'
|
||||
message += '</PlayReadyInitiator>'
|
||||
|
||||
} else if (client.type == WebOSDRMClientType.WIDEWINE) {
|
||||
} else if (client.type == WebOSDRMClientType.WIDEVINE) {
|
||||
drmMessageType = 'application/widevine+xml' // Message type for widevine 'xml'
|
||||
drmSystemId = 'urn:dvb:casystemid:19156' // Unique ID of DRM system
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
VokaInternalErrorData,
|
||||
VokaInternalErrorType
|
||||
} from '@/public/models/VokaError'
|
||||
import { IContent, IContextUpdated } from '@/public/@types'
|
||||
import { IContextUpdated } from '@/public/@types'
|
||||
|
||||
namespace VokaBusEvent {
|
||||
|
||||
@@ -33,6 +33,13 @@ namespace VokaBusEvent {
|
||||
label: string | null
|
||||
}
|
||||
|
||||
export interface ICon {
|
||||
id: string
|
||||
version: number
|
||||
}
|
||||
|
||||
export const taskCreated = createEventDefinition<ICon>()("task.created")
|
||||
|
||||
export const qualityChange = createEventDefinition<IChangeQuality>()('quality.update')
|
||||
|
||||
export const qualitySet = createEventDefinition<{
|
||||
@@ -56,7 +63,11 @@ namespace VokaBusEvent {
|
||||
|
||||
export const close = createEventDefinition()('close')
|
||||
|
||||
// audio
|
||||
// MARK: - Switch content
|
||||
export const switchContent =
|
||||
createEventDefinition<VokaCorePlayer.IContent>()('content.switch')
|
||||
|
||||
// MARK: - Audio
|
||||
export const audioTracksParsed = createEventDefinition<{
|
||||
audioTracks: any
|
||||
}>()('audioTracks.parsed')
|
||||
@@ -67,10 +78,6 @@ namespace VokaBusEvent {
|
||||
|
||||
// Others
|
||||
|
||||
export const contentRetrieved = createEventDefinition<{
|
||||
content: IContent
|
||||
}>()('content.loaded')
|
||||
|
||||
export const contextUpdated =
|
||||
createEventDefinition<IContextUpdated>()('context.update')
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import videojs from 'video.js'
|
||||
import Player from 'video.js/dist/types/player'
|
||||
import { EventBus } from 'ts-bus'
|
||||
import { createEventDefinition, EventBus } from 'ts-bus'
|
||||
import { VokaPlayerEvent} from '@/public/IVokaPlayer'
|
||||
import { AutoplayChecker } from '@/internal/utils/AutoplayChecker'
|
||||
import { Quality } from '@/public/models/ILoadOptions'
|
||||
@@ -23,7 +23,7 @@ import { Promise } from 'es6-promise'
|
||||
import ZoomModeObserver from '@/internal/observers/ZoomModeObserver'
|
||||
import CorePlayerOptions from '@/internal/player/CorePlayerOptions'
|
||||
import { bus } from '@/internal/events/bus'
|
||||
import VokaBusEvent from '@/internal/events/VokaBusEvent'
|
||||
import VokaBusEvent, { switchContent } from '@/internal/events/VokaBusEvent'
|
||||
|
||||
namespace VokaCorePlayer {
|
||||
|
||||
@@ -54,13 +54,20 @@ namespace VokaCorePlayer {
|
||||
export interface IMetrics {
|
||||
url: string
|
||||
interval: number | null
|
||||
params: { [key: string]: string }
|
||||
params: { [key: string]: string } | null
|
||||
}
|
||||
|
||||
export interface IHeartbeat {
|
||||
url: string
|
||||
interval: number
|
||||
version: number
|
||||
}
|
||||
|
||||
export enum DRMType {
|
||||
NONE = 'none',
|
||||
FAIRPLAY = 'fairplay',
|
||||
PLAYREADY = 'playready',
|
||||
WIDEWINE = 'widewine',
|
||||
WIDEVINE = 'widewine',
|
||||
}
|
||||
|
||||
export interface IDRMConfig {
|
||||
@@ -76,6 +83,7 @@ namespace VokaCorePlayer {
|
||||
drmConfig: IDRMConfig | null
|
||||
subtitlesUrl: string | null
|
||||
metrics: IMetrics | null
|
||||
heartbeat: IHeartbeat | null
|
||||
}
|
||||
|
||||
export function createPlayer(element: HTMLElement): PlayerCreationClosure {
|
||||
@@ -193,7 +201,7 @@ namespace VokaCorePlayer {
|
||||
}
|
||||
break
|
||||
case VokaContentType.WIDEVINE:
|
||||
if (drm != null && drm.type == DRMType.WIDEWINE) {
|
||||
if (drm != null && drm.type == DRMType.WIDEVINE) {
|
||||
playableContent = {
|
||||
type: "application/dash+xml",
|
||||
keySystemOptions: [{
|
||||
@@ -244,10 +252,12 @@ namespace VokaCorePlayer {
|
||||
if (playableContent != null) {
|
||||
playableContent['src'] = content.url
|
||||
playableContent['content'] = content.type
|
||||
playableContent['metrics'] = content.metrics
|
||||
playableContent['subtitlesUrl'] = content.subtitlesUrl
|
||||
|
||||
this.player.src(playableContent)
|
||||
|
||||
bus.publish(VokaBusEvent.switchContent(content))
|
||||
|
||||
return Promise.resolve()
|
||||
}
|
||||
return Promise.reject('Unsupported')
|
||||
|
||||
@@ -27,7 +27,7 @@ export default class VokaDashSourceHandler extends VokaSourceHandler {
|
||||
return ''
|
||||
}
|
||||
|
||||
// отключаем widewine на ios и в safari
|
||||
// отключаем widevine на ios и в safari
|
||||
if (source.content === VokaContentType.WIDEVINE && (Browser.IS_SAFARI || Browser.IS_IOS)) {
|
||||
return ''
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ export default class VokaWebOSSourceHandler extends VokaSourceHandler {
|
||||
const nativeDrm = this.createDRMPlayer(source, tech)
|
||||
VokaWebOSSourceHandler.nativeDrm = nativeDrm
|
||||
const type = source.content == VokaContentType.WIDEVINE
|
||||
? WebOSDRMClientType.WIDEWINE
|
||||
? WebOSDRMClientType.WIDEVINE
|
||||
: WebOSDRMClientType.PLAYREADY
|
||||
nativeDrm.loadClient(type)
|
||||
} else {
|
||||
|
||||
+111
-103
@@ -1,123 +1,131 @@
|
||||
import videojs, { VideoJsPlayer, VideoJsPlayerOptions } from 'video.js'
|
||||
import VokaEvent from "@/constants/VokaEvent"
|
||||
import videojs from 'video.js'
|
||||
import Player from 'video.js/dist/types/player'
|
||||
import { bus } from '@/internal/events/bus'
|
||||
import { IContent, IHeartbeat } from '@/public/@types'
|
||||
import { IHeartbeat } from '@/public/@types'
|
||||
import VokaBusEvent from '@/internal/events/VokaBusEvent'
|
||||
import TimerUtils from '@/internal/utils/TimerUtils'
|
||||
import { HTTPMethod, IHTTPClient, HTTPClient, RequestOptions } from '@/internal/utils/HTTPClient'
|
||||
const Plugin = videojs.getPlugin('plugin')
|
||||
import { HTTPMethod, IHTTPClient, HTTPClient } from '@/internal/utils/HTTPClient'
|
||||
|
||||
export class VokaHeartbeatPlugin extends Plugin {
|
||||
private player!: VideoJsPlayer
|
||||
private heartbeat: IHeartbeat | null
|
||||
private playbackStarted: boolean
|
||||
private scheduledProgress: ReturnType<typeof setInterval> | null
|
||||
private httpClient: IHTTPClient
|
||||
namespace VokaHeartbeatPlugin {
|
||||
|
||||
constructor(player: VideoJsPlayer, options: VideoJsPlayerOptions) {
|
||||
super(player, options)
|
||||
this.player = player
|
||||
this.playbackStarted = false
|
||||
this.httpClient = new HTTPClient()
|
||||
this.setupListeners()
|
||||
}
|
||||
const VideoPlugin = videojs.getPlugin('plugin')
|
||||
|
||||
private setupListeners() {
|
||||
bus.subscribe(
|
||||
VokaBusEvent.contentRetrieved,
|
||||
(event) => {
|
||||
this.heartbeat = event.payload.content.heartbeat
|
||||
export class Plugin extends VideoPlugin {
|
||||
|
||||
private player!: Player
|
||||
private heartbeat: IHeartbeat | null
|
||||
private playbackStarted: boolean
|
||||
private scheduledProgress: ReturnType<typeof setInterval> | null
|
||||
private httpClient: IHTTPClient
|
||||
|
||||
constructor(player: Player) {
|
||||
super(player)
|
||||
this.player = player
|
||||
this.playbackStarted = false
|
||||
this.httpClient = new HTTPClient()
|
||||
|
||||
this.setupListeners()
|
||||
}
|
||||
|
||||
private setupListeners() {
|
||||
bus.subscribe(
|
||||
VokaBusEvent.switchContent,
|
||||
(event) => {
|
||||
this.unscheduleProgressRequest()
|
||||
this.heartbeat = event.payload.heartbeat
|
||||
}
|
||||
)
|
||||
|
||||
this.player.on('play', this.onPlaybackStarted.bind(this))
|
||||
this.player.on('ended', this.onPlaybackEnded.bind(this))
|
||||
}
|
||||
|
||||
private onPlaybackStarted(event) {
|
||||
if (this.heartbeat == null || this.playbackStarted) { return }
|
||||
this.playbackStarted = true
|
||||
this.createRequest(this.heartbeat, 'start')
|
||||
this.scheduleProgressRequest(this.heartbeat)
|
||||
}
|
||||
|
||||
private onPlaybackEnded(event) {
|
||||
this.unscheduleProgressRequest()
|
||||
if (this.heartbeat == null || !this.playbackStarted) { return }
|
||||
this.playbackStarted = false
|
||||
this.createRequest(this.heartbeat, 'end')
|
||||
}
|
||||
|
||||
private createRequest(heartbeat: IHeartbeat, type: string) {
|
||||
let needTime = false
|
||||
let needDuration = false
|
||||
let params = {}
|
||||
switch (type) {
|
||||
case 'start':
|
||||
params['action'] = 'start'
|
||||
needTime = true
|
||||
needDuration = true
|
||||
break
|
||||
case 'end':
|
||||
params['action'] = 'end'
|
||||
needTime = true
|
||||
needDuration = true
|
||||
break
|
||||
case 'progress':
|
||||
params['action'] = 'watch'
|
||||
needTime = true
|
||||
needDuration = true
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
)
|
||||
|
||||
this.player.on('play', this.onPlaybackStarted.bind(this))
|
||||
this.player.on('ended', this.onPlaybackEnded.bind(this))
|
||||
}
|
||||
|
||||
private onPlaybackStarted(event) {
|
||||
|
||||
if (this.heartbeat == null || this.playbackStarted) { return }
|
||||
this.playbackStarted = true
|
||||
|
||||
this.createRequest('start')
|
||||
this.scheduleProgressRequest()
|
||||
}
|
||||
|
||||
private onPlaybackEnded(event) {
|
||||
|
||||
if (this.heartbeat == null || !this.playbackStarted) { return }
|
||||
this.playbackStarted = false
|
||||
|
||||
this.createRequest('end')
|
||||
this.unscheduleProgressRequest()
|
||||
}
|
||||
|
||||
private createRequest(type: string) {
|
||||
let needTime = false
|
||||
let needDuration = false
|
||||
let params = {}
|
||||
switch (type) {
|
||||
case 'start':
|
||||
params['action'] = 'start'
|
||||
needTime = true
|
||||
needDuration = true
|
||||
break
|
||||
case 'end':
|
||||
params['action'] = 'end'
|
||||
needTime = true
|
||||
needDuration = true
|
||||
break
|
||||
case 'progress':
|
||||
params['action'] = 'watch'
|
||||
needTime = true
|
||||
needDuration = true
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
if (!isNaN(this.heartbeat.version)) {
|
||||
params['v'] = this.heartbeat.version.toString()
|
||||
}
|
||||
|
||||
if (needTime) {
|
||||
const value = this.player.currentTime()
|
||||
if (!isNaN(value)) {
|
||||
params['timestamp'] = Math.round(value * 1000).toString()
|
||||
if (!isNaN(heartbeat.version)) {
|
||||
params['v'] = heartbeat.version.toString()
|
||||
}
|
||||
}
|
||||
|
||||
if (needDuration) {
|
||||
const value = this.player.duration()
|
||||
if (!isNaN(value)) {
|
||||
params['duration'] = Math.round(value * 1000).toString()
|
||||
if (needTime) {
|
||||
const value = this.player.currentTime()
|
||||
if (value != undefined && !isNaN(value)) {
|
||||
params['timestamp'] = Math.round(value * 1000).toString()
|
||||
}
|
||||
}
|
||||
|
||||
if (needDuration) {
|
||||
const value = this.player.duration()
|
||||
if (value != undefined && !isNaN(value)) {
|
||||
params['duration'] = Math.round(value * 1000).toString()
|
||||
}
|
||||
}
|
||||
|
||||
const request = this.httpClient.request(
|
||||
HTTPMethod.GET,
|
||||
heartbeat.url,
|
||||
params,
|
||||
null,
|
||||
{ timeout: 60000 }
|
||||
)
|
||||
}
|
||||
|
||||
const request = this.httpClient.request(
|
||||
HTTPMethod.GET,
|
||||
this.heartbeat.url,
|
||||
params,
|
||||
null,
|
||||
{ timeout: 60000 }
|
||||
)
|
||||
}
|
||||
private scheduleProgressRequest(heartbeat: IHeartbeat) {
|
||||
this.unscheduleProgressRequest()
|
||||
this.scheduledProgress = TimerUtils.setInterval(
|
||||
() => { this.createProgressRequest(heartbeat) },
|
||||
heartbeat.interval * 1000,
|
||||
)
|
||||
}
|
||||
|
||||
private scheduleProgressRequest() {
|
||||
this.unscheduleProgressRequest()
|
||||
this.scheduledProgress = TimerUtils.setInterval(() => { this.createProgressRequest() }, this.heartbeat.interval * 1000)
|
||||
}
|
||||
private unscheduleProgressRequest() {
|
||||
if (this.scheduledProgress == null) { return }
|
||||
clearInterval(this.scheduledProgress)
|
||||
this.scheduledProgress = null
|
||||
}
|
||||
|
||||
private unscheduleProgressRequest() {
|
||||
if (this.scheduledProgress == null) { return }
|
||||
clearInterval(this.scheduledProgress)
|
||||
this.scheduledProgress = null
|
||||
}
|
||||
private createProgressRequest(heartbeat: IHeartbeat) {
|
||||
this.createRequest(heartbeat, 'progress')
|
||||
}
|
||||
|
||||
private createProgressRequest() {
|
||||
this.createRequest('progress')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
videojs.registerPlugin('vokaHeartbeatPlugin', VokaHeartbeatPlugin)
|
||||
videojs.registerPlugin('vokaHeartbeatPlugin', VokaHeartbeatPlugin.Plugin)
|
||||
export default VokaHeartbeatPlugin
|
||||
+121
-115
@@ -1,129 +1,135 @@
|
||||
import videojs, { VideoJsPlayer, VideoJsPlayerOptions } from 'video.js'
|
||||
import keycode from 'keycode'
|
||||
const Plugin = videojs.getPlugin('plugin')
|
||||
|
||||
export class VokaKeyboardPlugin extends Plugin {
|
||||
private player!: VideoJsPlayer
|
||||
private options: VideoJsPlayerOptions
|
||||
namespace VokaKeyboardPlugin {
|
||||
|
||||
constructor(player: VideoJsPlayer, options: VideoJsPlayerOptions) {
|
||||
super(player, options)
|
||||
this.player = player
|
||||
this.options = options
|
||||
this.setupListeners()
|
||||
}
|
||||
const VideoPlugin = videojs.getPlugin('plugin')
|
||||
|
||||
private setupListeners() {
|
||||
document.addEventListener('keyup', this.listenerKeyUp.bind(this))
|
||||
}
|
||||
export class Plugin extends VideoPlugin {
|
||||
private player!: VideoJsPlayer
|
||||
private options: VideoJsPlayerOptions
|
||||
|
||||
private listenerKeyUp(event: KeyboardEvent) {
|
||||
if (event.defaultPrevented || !this.player) {
|
||||
return
|
||||
}
|
||||
switch (keycode(event)) {
|
||||
case 'space':
|
||||
case 'enter':
|
||||
this.operationTogglePlay()
|
||||
break
|
||||
|
||||
case 'up':
|
||||
this.operationChangeVolume(true)
|
||||
break
|
||||
|
||||
case 'down':
|
||||
this.operationChangeVolume(false)
|
||||
break
|
||||
|
||||
case 'm':
|
||||
this.operationMuteSound()
|
||||
break
|
||||
|
||||
case 'left':
|
||||
this.operationStepBackward()
|
||||
break
|
||||
|
||||
case 'right':
|
||||
this.operationStepForward()
|
||||
break
|
||||
|
||||
case 'f':
|
||||
this.operationFullscreenToggle()
|
||||
break
|
||||
constructor(player: VideoJsPlayer, options: VideoJsPlayerOptions) {
|
||||
super(player, options)
|
||||
this.player = player
|
||||
this.options = options
|
||||
this.setupListeners()
|
||||
}
|
||||
|
||||
// Отменяем действие по умолчанию в плеере.
|
||||
event.preventDefault()
|
||||
}
|
||||
private setupListeners() {
|
||||
document.addEventListener('keyup', this.listenerKeyUp.bind(this))
|
||||
}
|
||||
|
||||
private operationTogglePlay() {
|
||||
if (this.player.paused()) {
|
||||
this.player.play()
|
||||
} else {
|
||||
this.player.pause()
|
||||
private listenerKeyUp(event: KeyboardEvent) {
|
||||
if (event.defaultPrevented || !this.player) {
|
||||
return
|
||||
}
|
||||
switch (keycode(event)) {
|
||||
case 'space':
|
||||
case 'enter':
|
||||
this.operationTogglePlay()
|
||||
break
|
||||
|
||||
case 'up':
|
||||
this.operationChangeVolume(true)
|
||||
break
|
||||
|
||||
case 'down':
|
||||
this.operationChangeVolume(false)
|
||||
break
|
||||
|
||||
case 'm':
|
||||
this.operationMuteSound()
|
||||
break
|
||||
|
||||
case 'left':
|
||||
this.operationStepBackward()
|
||||
break
|
||||
|
||||
case 'right':
|
||||
this.operationStepForward()
|
||||
break
|
||||
|
||||
case 'f':
|
||||
this.operationFullscreenToggle()
|
||||
break
|
||||
}
|
||||
|
||||
// Отменяем действие по умолчанию в плеере.
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
private operationTogglePlay() {
|
||||
if (this.player.paused()) {
|
||||
this.player.play()
|
||||
} else {
|
||||
this.player.pause()
|
||||
}
|
||||
}
|
||||
|
||||
private operationChangeVolume(rise: Boolean) {
|
||||
const step = 0.1
|
||||
const value = this.player.volume() + (rise ? step : -step)
|
||||
this.player.volume(value)
|
||||
}
|
||||
|
||||
private operationMuteSound() {
|
||||
this.player.muted(!this.player.muted())
|
||||
}
|
||||
|
||||
private operationFullscreenToggle() {
|
||||
if (!this.player.isFullscreen()) {
|
||||
this.player.requestFullscreen()
|
||||
} else {
|
||||
this.player.exitFullscreen()
|
||||
}
|
||||
}
|
||||
|
||||
private operationStepBackward() {
|
||||
const skipTime = this.options.skip?.forward
|
||||
if (!skipTime) return
|
||||
|
||||
const currentVideoTime = this.player.currentTime()
|
||||
const liveTracker = this.player.liveTracker
|
||||
const seekableStart =
|
||||
liveTracker && liveTracker.isLive() && liveTracker.seekableStart()
|
||||
let newTime
|
||||
|
||||
if (seekableStart && currentVideoTime - skipTime <= seekableStart) {
|
||||
newTime = seekableStart
|
||||
} else if (currentVideoTime >= skipTime) {
|
||||
newTime = currentVideoTime - skipTime
|
||||
} else {
|
||||
newTime = 0
|
||||
}
|
||||
this.player.currentTime(newTime)
|
||||
}
|
||||
|
||||
private operationStepForward() {
|
||||
const skipTime = this.options.skip?.forward
|
||||
if (isNaN(this.player.duration()) || !skipTime) {
|
||||
return
|
||||
}
|
||||
|
||||
const currentVideoTime = this.player.currentTime()
|
||||
const liveTracker = this.player.liveTracker
|
||||
const duration =
|
||||
liveTracker && liveTracker.isLive()
|
||||
? liveTracker.seekableEnd()
|
||||
: this.player.duration()
|
||||
let newTime
|
||||
|
||||
if (currentVideoTime + skipTime <= duration) {
|
||||
newTime = currentVideoTime + skipTime
|
||||
} else {
|
||||
newTime = duration
|
||||
}
|
||||
|
||||
this.player.currentTime(newTime)
|
||||
}
|
||||
}
|
||||
|
||||
private operationChangeVolume(rise: Boolean) {
|
||||
const step = 0.1
|
||||
const value = this.player.volume() + (rise ? step : -step)
|
||||
this.player.volume(value)
|
||||
}
|
||||
|
||||
private operationMuteSound() {
|
||||
this.player.muted(!this.player.muted())
|
||||
}
|
||||
|
||||
private operationFullscreenToggle() {
|
||||
if (!this.player.isFullscreen()) {
|
||||
this.player.requestFullscreen()
|
||||
} else {
|
||||
this.player.exitFullscreen()
|
||||
}
|
||||
}
|
||||
|
||||
private operationStepBackward() {
|
||||
const skipTime = this.options.skip?.forward
|
||||
if (!skipTime) return
|
||||
|
||||
const currentVideoTime = this.player.currentTime()
|
||||
const liveTracker = this.player.liveTracker
|
||||
const seekableStart =
|
||||
liveTracker && liveTracker.isLive() && liveTracker.seekableStart()
|
||||
let newTime
|
||||
|
||||
if (seekableStart && currentVideoTime - skipTime <= seekableStart) {
|
||||
newTime = seekableStart
|
||||
} else if (currentVideoTime >= skipTime) {
|
||||
newTime = currentVideoTime - skipTime
|
||||
} else {
|
||||
newTime = 0
|
||||
}
|
||||
this.player.currentTime(newTime)
|
||||
}
|
||||
|
||||
private operationStepForward() {
|
||||
const skipTime = this.options.skip?.forward
|
||||
if (isNaN(this.player.duration()) || !skipTime) {
|
||||
return
|
||||
}
|
||||
|
||||
const currentVideoTime = this.player.currentTime()
|
||||
const liveTracker = this.player.liveTracker
|
||||
const duration =
|
||||
liveTracker && liveTracker.isLive()
|
||||
? liveTracker.seekableEnd()
|
||||
: this.player.duration()
|
||||
let newTime
|
||||
|
||||
if (currentVideoTime + skipTime <= duration) {
|
||||
newTime = currentVideoTime + skipTime
|
||||
} else {
|
||||
newTime = duration
|
||||
}
|
||||
|
||||
this.player.currentTime(newTime)
|
||||
}
|
||||
}
|
||||
|
||||
videojs.registerPlugin('vokaKeyboardPlugin', VokaKeyboardPlugin)
|
||||
videojs.registerPlugin('vokaKeyboardPlugin', VokaKeyboardPlugin.Plugin)
|
||||
export default VokaKeyboardPlugin
|
||||
|
||||
@@ -1,128 +1,134 @@
|
||||
import videojs, { VideoJsPlayer, VideoJsPlayerOptions } from 'video.js'
|
||||
const Plugin = videojs.getPlugin('plugin')
|
||||
|
||||
const KEY_CODES = {
|
||||
ENTER: 13,
|
||||
namespace VokaMagicRemotePlugin {
|
||||
|
||||
LEFT: 37,
|
||||
UP: 38,
|
||||
RIGHT: 39,
|
||||
DOWN: 40,
|
||||
const VideoPlugin = videojs.getPlugin('plugin')
|
||||
|
||||
STOP: 413,
|
||||
PLAY: 415,
|
||||
PAUSE: 19,
|
||||
const KEY_CODES = {
|
||||
ENTER: 13,
|
||||
|
||||
NEXT: 417,
|
||||
PREV: 412
|
||||
}
|
||||
LEFT: 37,
|
||||
UP: 38,
|
||||
RIGHT: 39,
|
||||
DOWN: 40,
|
||||
|
||||
export class VokaMagicRemotePlugin extends Plugin {
|
||||
private player!: VideoJsPlayer
|
||||
private options: VideoJsPlayerOptions
|
||||
STOP: 413,
|
||||
PLAY: 415,
|
||||
PAUSE: 19,
|
||||
|
||||
constructor(player: VideoJsPlayer, options: VideoJsPlayerOptions) {
|
||||
super(player, options)
|
||||
this.player = player
|
||||
this.options = options
|
||||
this.setupListeners()
|
||||
NEXT: 417,
|
||||
PREV: 412
|
||||
}
|
||||
|
||||
private setupListeners() {
|
||||
document.addEventListener('keyup', this.listenerKeyUp.bind(this))
|
||||
}
|
||||
export class Plugin extends VideoPlugin {
|
||||
private player!: VideoJsPlayer
|
||||
private options: VideoJsPlayerOptions
|
||||
|
||||
private listenerKeyUp(event: KeyboardEvent) {
|
||||
if (event.defaultPrevented || !this.player) {
|
||||
return
|
||||
}
|
||||
switch (event.keyCode) {
|
||||
case KEY_CODES.PLAY:
|
||||
this.operationPlay()
|
||||
break
|
||||
case KEY_CODES.PAUSE:
|
||||
this.operationPause()
|
||||
break
|
||||
case KEY_CODES.STOP:
|
||||
this.operationPause()
|
||||
break
|
||||
case KEY_CODES.ENTER:
|
||||
this.operationTogglePlay()
|
||||
break
|
||||
case KEY_CODES.LEFT:
|
||||
case KEY_CODES.PREV:
|
||||
this.operationStepBackward()
|
||||
break
|
||||
case KEY_CODES.RIGHT:
|
||||
case KEY_CODES.NEXT:
|
||||
this.operationStepForward()
|
||||
break
|
||||
constructor(player: VideoJsPlayer, options: VideoJsPlayerOptions) {
|
||||
super(player, options)
|
||||
this.player = player
|
||||
this.options = options
|
||||
this.setupListeners()
|
||||
}
|
||||
|
||||
// Отменяем действие по умолчанию в плеере.
|
||||
event.preventDefault()
|
||||
}
|
||||
private setupListeners() {
|
||||
document.addEventListener('keyup', this.listenerKeyUp.bind(this))
|
||||
}
|
||||
|
||||
private operationTogglePlay() {
|
||||
if (this.player.paused()) {
|
||||
this.player.play()
|
||||
} else {
|
||||
private listenerKeyUp(event: KeyboardEvent) {
|
||||
if (event.defaultPrevented || !this.player) {
|
||||
return
|
||||
}
|
||||
switch (event.keyCode) {
|
||||
case KEY_CODES.PLAY:
|
||||
this.operationPlay()
|
||||
break
|
||||
case KEY_CODES.PAUSE:
|
||||
this.operationPause()
|
||||
break
|
||||
case KEY_CODES.STOP:
|
||||
this.operationPause()
|
||||
break
|
||||
case KEY_CODES.ENTER:
|
||||
this.operationTogglePlay()
|
||||
break
|
||||
case KEY_CODES.LEFT:
|
||||
case KEY_CODES.PREV:
|
||||
this.operationStepBackward()
|
||||
break
|
||||
case KEY_CODES.RIGHT:
|
||||
case KEY_CODES.NEXT:
|
||||
this.operationStepForward()
|
||||
break
|
||||
}
|
||||
|
||||
// Отменяем действие по умолчанию в плеере.
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
private operationTogglePlay() {
|
||||
if (this.player.paused()) {
|
||||
this.player.play()
|
||||
} else {
|
||||
this.player.pause()
|
||||
}
|
||||
}
|
||||
|
||||
private operationPlay() {
|
||||
if (this.player.paused()) {
|
||||
this.player.play()
|
||||
}
|
||||
}
|
||||
|
||||
private operationPause() {
|
||||
this.player.pause()
|
||||
}
|
||||
}
|
||||
|
||||
private operationPlay() {
|
||||
if (this.player.paused()) {
|
||||
this.player.play()
|
||||
private operationStepBackward() {
|
||||
const skipTime = this.options.skip?.forward
|
||||
if (!skipTime) return
|
||||
|
||||
const currentVideoTime = this.player.currentTime()
|
||||
const liveTracker = this.player.liveTracker
|
||||
const seekableStart =
|
||||
liveTracker && liveTracker.isLive() && liveTracker.seekableStart()
|
||||
let newTime
|
||||
|
||||
if (seekableStart && currentVideoTime - skipTime <= seekableStart) {
|
||||
newTime = seekableStart
|
||||
} else if (currentVideoTime >= skipTime) {
|
||||
newTime = currentVideoTime - skipTime
|
||||
} else {
|
||||
newTime = 0
|
||||
}
|
||||
this.player.currentTime(newTime)
|
||||
}
|
||||
|
||||
private operationStepForward() {
|
||||
const skipTime = this.options.skip?.forward
|
||||
if (isNaN(this.player.duration()) || !skipTime) {
|
||||
return
|
||||
}
|
||||
|
||||
const currentVideoTime = this.player.currentTime()
|
||||
const liveTracker = this.player.liveTracker
|
||||
const duration =
|
||||
liveTracker && liveTracker.isLive()
|
||||
? liveTracker.seekableEnd()
|
||||
: this.player.duration()
|
||||
let newTime
|
||||
|
||||
if (currentVideoTime + skipTime <= duration) {
|
||||
newTime = currentVideoTime + skipTime
|
||||
} else {
|
||||
newTime = duration
|
||||
}
|
||||
|
||||
this.player.currentTime(newTime)
|
||||
}
|
||||
}
|
||||
|
||||
private operationPause() {
|
||||
this.player.pause()
|
||||
}
|
||||
|
||||
private operationStepBackward() {
|
||||
const skipTime = this.options.skip?.forward
|
||||
if (!skipTime) return
|
||||
|
||||
const currentVideoTime = this.player.currentTime()
|
||||
const liveTracker = this.player.liveTracker
|
||||
const seekableStart =
|
||||
liveTracker && liveTracker.isLive() && liveTracker.seekableStart()
|
||||
let newTime
|
||||
|
||||
if (seekableStart && currentVideoTime - skipTime <= seekableStart) {
|
||||
newTime = seekableStart
|
||||
} else if (currentVideoTime >= skipTime) {
|
||||
newTime = currentVideoTime - skipTime
|
||||
} else {
|
||||
newTime = 0
|
||||
}
|
||||
this.player.currentTime(newTime)
|
||||
}
|
||||
|
||||
private operationStepForward() {
|
||||
const skipTime = this.options.skip?.forward
|
||||
if (isNaN(this.player.duration()) || !skipTime) {
|
||||
return
|
||||
}
|
||||
|
||||
const currentVideoTime = this.player.currentTime()
|
||||
const liveTracker = this.player.liveTracker
|
||||
const duration =
|
||||
liveTracker && liveTracker.isLive()
|
||||
? liveTracker.seekableEnd()
|
||||
: this.player.duration()
|
||||
let newTime
|
||||
|
||||
if (currentVideoTime + skipTime <= duration) {
|
||||
newTime = currentVideoTime + skipTime
|
||||
} else {
|
||||
newTime = duration
|
||||
}
|
||||
|
||||
this.player.currentTime(newTime)
|
||||
}
|
||||
}
|
||||
|
||||
videojs.registerPlugin('vokaMagicRemotePlugin', VokaMagicRemotePlugin)
|
||||
videojs.registerPlugin('vokaMagicRemotePlugin', VokaMagicRemotePlugin.Plugin)
|
||||
export default VokaMagicRemotePlugin
|
||||
|
||||
+280
-281
@@ -1,316 +1,315 @@
|
||||
import videojs, { VideoJsPlayer, VideoJsPlayerOptions } from 'video.js'
|
||||
import VokaEvent from "@/constants/VokaEvent"
|
||||
import Player from 'video.js/dist/types/player'
|
||||
import { bus } from '@/internal/events/bus'
|
||||
import { IContent, IMetrics, IMetricsParams } from '@/public/@types'
|
||||
import VokaBusEvent from '@/internal/events/VokaBusEvent'
|
||||
import GUIDUtils from '@/internal/utils/GUIDUtils'
|
||||
import TimerUtils from '@/internal/utils/TimerUtils'
|
||||
import BrowserUtils from '@/internal/utils/BrowserUtils'
|
||||
import { HTTPMethod, IHTTPClient, HTTPClient, RequestOptions } from '@/internal/utils/HTTPClient'
|
||||
import { IHTTPClient, HTTPClient } from '@/internal/utils/HTTPClient'
|
||||
import DateUtils from '@/internal/utils/DateUtils'
|
||||
import VokaCorePlayer from '@/internal/player/VokaCorePlayer'
|
||||
import videojs from 'video.js'
|
||||
|
||||
const Plugin = videojs.getPlugin('plugin')
|
||||
const VideoPlugin = videojs.getPlugin('plugin')
|
||||
|
||||
interface ITimeSpend {
|
||||
initializing_player: number
|
||||
initializing_buffer: number
|
||||
playing: number
|
||||
paused: number
|
||||
buffering: number
|
||||
}
|
||||
namespace VokaMetricsPlugin {
|
||||
|
||||
export class VokaMetricsPlugin extends Plugin {
|
||||
private player!: VideoJsPlayer
|
||||
private playbackInitialized: Boolean
|
||||
private playbackStarted: Boolean
|
||||
private prevPlayerState: string
|
||||
private prevStateTime: number
|
||||
private metrics: IMetrics | null
|
||||
private httpClient: IHTTPClient
|
||||
private defaultWatchSessionId: string
|
||||
private timeSpent: ITimeSpend | null
|
||||
private scheduledRequest: ReturnType<typeof setInterval> | null
|
||||
private timeout: number = 2000
|
||||
|
||||
constructor(player: VideoJsPlayer, options: VideoJsPlayerOptions) {
|
||||
super(player, options)
|
||||
this.player = player
|
||||
this.httpClient = new HTTPClient()
|
||||
this.playbackInitialized = false
|
||||
this.playbackStarted = false
|
||||
this.prevPlayerState = ''
|
||||
this.prevStateTime = 0
|
||||
this.metrics = null
|
||||
this.timeSpent = null
|
||||
|
||||
this.setupListeners()
|
||||
interface ITimeSpend {
|
||||
initializing_player: number
|
||||
initializing_buffer: number
|
||||
playing: number
|
||||
paused: number
|
||||
buffering: number
|
||||
}
|
||||
|
||||
private setupListeners() {
|
||||
bus.subscribe(
|
||||
VokaBusEvent.contentRetrieved,
|
||||
(event) => {
|
||||
this.onSourceAttached(event.payload.content)
|
||||
}
|
||||
)
|
||||
this.player.on('play', this.onPlaybackStarted.bind(this))
|
||||
this.player.on('pause', this.onPlaybackPaused.bind(this))
|
||||
this.player.on('ended', this.onPlaybackEnded.bind(this))
|
||||
this.player.on('canplay', this.onPlayerCanplay.bind(this))
|
||||
// TODO!
|
||||
// player.addEventListener('bufferingUpdate', onPlayerBufferingUpdate)
|
||||
}
|
||||
export class Plugin extends VideoPlugin {
|
||||
private player!: Player
|
||||
private playbackInitialized: Boolean
|
||||
private playbackStarted: Boolean
|
||||
private prevPlayerState: string
|
||||
private prevStateTime: number
|
||||
private metrics: VokaCorePlayer.IMetrics | null
|
||||
private httpClient: IHTTPClient
|
||||
private defaultWatchSessionId: string
|
||||
private timeSpent: ITimeSpend | null
|
||||
private scheduledRequest: ReturnType<typeof setInterval> | null
|
||||
private timeout: number = 2000
|
||||
|
||||
// Listeners
|
||||
constructor(player: Player) {
|
||||
super(player)
|
||||
|
||||
private onPlayerBufferingUpdate(event) {
|
||||
if (this.metrics == null) { return }
|
||||
this.createRequest('update')
|
||||
}
|
||||
this.player = player
|
||||
this.httpClient = new HTTPClient()
|
||||
this.playbackInitialized = false
|
||||
this.playbackStarted = false
|
||||
this.prevPlayerState = ''
|
||||
this.prevStateTime = 0
|
||||
this.metrics = null
|
||||
this.timeSpent = null
|
||||
|
||||
private onSourceAttached(content: IContent) {
|
||||
this.onPlaybackEnded(null)
|
||||
|
||||
this.defaultWatchSessionId = GUIDUtils.generateGUID()
|
||||
|
||||
this.metrics = content.metrics
|
||||
this.playbackInitialized = false
|
||||
this.playbackStarted = false
|
||||
this.prevPlayerState = ''
|
||||
this.prevStateTime = 0
|
||||
this.timeSpent = null
|
||||
|
||||
if (this.metrics == null) { return }
|
||||
|
||||
this.scheduleRequestIfNeeded()
|
||||
|
||||
this.createRequest('init')
|
||||
this.createRequest('buffering')
|
||||
}
|
||||
|
||||
private onPlaybackStarted(event) {
|
||||
if (this.metrics == null) { return }
|
||||
|
||||
this.playbackInitialized = true
|
||||
this.playbackStarted = true
|
||||
|
||||
this.scheduleRequestIfNeeded()
|
||||
|
||||
this.createRequest('play')
|
||||
}
|
||||
|
||||
private onPlaybackEnded(event) {
|
||||
this.unscheduleRequest()
|
||||
this.playbackStarted = false
|
||||
}
|
||||
|
||||
private onPlayerCanplay(event) {
|
||||
if (this.metrics == null) { return }
|
||||
|
||||
this.playbackInitialized = true
|
||||
|
||||
this.createRequest('update')
|
||||
this.scheduleRequestIfNeeded()
|
||||
}
|
||||
|
||||
private onPlaybackPaused(event) {
|
||||
if (this.metrics == null) { return }
|
||||
|
||||
this.scheduleRequestIfNeeded()
|
||||
this.createRequest('pause')
|
||||
}
|
||||
|
||||
private scheduleRequestIfNeeded() {
|
||||
if (this.scheduledRequest !== undefined) { return }
|
||||
this.scheduleRequest()
|
||||
}
|
||||
|
||||
private scheduleRequest() {
|
||||
this.unscheduleRequest()
|
||||
if (this.metrics == null) { return }
|
||||
this.scheduledRequest = TimerUtils.setInterval(
|
||||
() => { this.createRequest('periodic') },
|
||||
this.metrics.interval * 1000
|
||||
)
|
||||
}
|
||||
|
||||
private unscheduleRequest() {
|
||||
if (this.scheduledRequest == null) { return }
|
||||
clearInterval(this.scheduledRequest)
|
||||
this.scheduledRequest = null
|
||||
}
|
||||
|
||||
private createRequest(state: string) {
|
||||
if (this.metrics == null) { return }
|
||||
const params = this.metrics.params || {}
|
||||
|
||||
let playerState = this.getPlayerState()
|
||||
switch (state) {
|
||||
case 'init':
|
||||
playerState = 'initialization'
|
||||
break
|
||||
case 'buffering':
|
||||
playerState = 'initial_buffering'
|
||||
break
|
||||
case 'play':
|
||||
playerState = 'playing'
|
||||
break
|
||||
case 'pause':
|
||||
playerState = 'paused'
|
||||
break
|
||||
case 'periodic':
|
||||
case 'update':
|
||||
default:
|
||||
break
|
||||
this.setupListeners()
|
||||
}
|
||||
|
||||
if (state === 'update' && playerState === this.prevPlayerState) { return }
|
||||
private setupListeners() {
|
||||
|
||||
this.updateTimeSpent()
|
||||
this.prevPlayerState = playerState
|
||||
bus.subscribe(
|
||||
VokaBusEvent.switchContent,
|
||||
(event) => {
|
||||
this.onSourceAttached(event.payload)
|
||||
}
|
||||
)
|
||||
|
||||
if (state != 'periodic') { return }
|
||||
|
||||
if (state == 'periodic') {
|
||||
this.timeout = this.metrics.interval * 2 * 1000
|
||||
} else {
|
||||
this.timeout = 20000
|
||||
}
|
||||
|
||||
const osVersion = BrowserUtils.getOSVersion()
|
||||
let deviceOS = osVersion.name.toLowerCase()
|
||||
switch (deviceOS) {
|
||||
case 'ios':
|
||||
case 'android':
|
||||
case 'windows':
|
||||
case 'macos':
|
||||
case 'linux':
|
||||
break
|
||||
default:
|
||||
deviceOS = 'other'
|
||||
break
|
||||
}
|
||||
|
||||
// TODO!
|
||||
let bandwidth = NaN//player.getNetworkBandwidth();
|
||||
if (isNaN(bandwidth)) {
|
||||
bandwidth = 0
|
||||
} else {
|
||||
bandwidth *= 1000
|
||||
}
|
||||
|
||||
const data = {
|
||||
application_id: params.application_id,
|
||||
application_version: params.application_version,
|
||||
user_type: params.user_type,
|
||||
user_id: params.user_id,
|
||||
device_id: BrowserUtils.deviceID,
|
||||
device_os: deviceOS,
|
||||
device_type: 'browser',
|
||||
device_player_type: 'native',
|
||||
application_session_id: GUIDUtils.sessionGUID,
|
||||
watch_session_id: this.getWatchSessionId(),
|
||||
resource_uid: params.resource_uid,
|
||||
resource_type: params.resource_type,
|
||||
this.player.on('play', this.onPlaybackStarted.bind(this))
|
||||
this.player.on('pause', this.onPlaybackPaused.bind(this))
|
||||
this.player.on('ended', this.onPlaybackEnded.bind(this))
|
||||
this.player.on('canplay', this.onPlayerCanplay.bind(this))
|
||||
// TODO!
|
||||
// buffered_duration: Math.floor(player.getBufferLength() * 1000),
|
||||
bandwidth: Math.floor(bandwidth),
|
||||
player_state: playerState,
|
||||
time_spent: this.timeSpent,
|
||||
network_type: 'unknown'
|
||||
};
|
||||
|
||||
/*const request = this.httpClient.request(
|
||||
HTTPMethod.POST,
|
||||
this.getApiUrl(),
|
||||
null,
|
||||
data,
|
||||
{ timeout: timeout, withCredentials: true }
|
||||
)*/
|
||||
|
||||
this.timeSpent = null
|
||||
}
|
||||
|
||||
private getApiUrl(): string | null {
|
||||
if (this.metrics == null) { return null }
|
||||
|
||||
if (this.metrics.apiUrl != null) {
|
||||
return this.metrics.apiUrl
|
||||
// player.addEventListener('bufferingUpdate', onPlayerBufferingUpdate)
|
||||
}
|
||||
|
||||
let result = this.metrics.apiHost
|
||||
if (result == null) {
|
||||
result = '127.0.0.1'
|
||||
}
|
||||
|
||||
if (result.indexOf('//') < 0) {
|
||||
result = 'https://' + result
|
||||
}
|
||||
return result + '/v2/player'
|
||||
}
|
||||
// Listeners
|
||||
|
||||
private getWatchSessionId() {
|
||||
if (this.metrics != null) {
|
||||
const params = this.metrics.params
|
||||
if (params != null && params.watch_session_id != null) {
|
||||
return params.watch_session_id
|
||||
private onPlayerBufferingUpdate(event) {
|
||||
if (this.metrics == null) { return }
|
||||
this.createRequest(this.metrics, 'update')
|
||||
}
|
||||
|
||||
private onSourceAttached(content: VokaCorePlayer.IContent) {
|
||||
this.onPlaybackEnded(null)
|
||||
|
||||
this.defaultWatchSessionId = GUIDUtils.generateGUID()
|
||||
|
||||
this.metrics = content.metrics
|
||||
this.playbackInitialized = false
|
||||
this.playbackStarted = false
|
||||
this.prevPlayerState = ''
|
||||
this.prevStateTime = 0
|
||||
this.timeSpent = null
|
||||
|
||||
if (this.metrics == null) { return }
|
||||
|
||||
this.scheduleRequestIfNeeded()
|
||||
|
||||
this.createRequest(this.metrics, 'init')
|
||||
this.createRequest(this.metrics, 'buffering')
|
||||
}
|
||||
|
||||
private onPlaybackStarted(event) {
|
||||
if (this.metrics == null) { return }
|
||||
|
||||
this.playbackInitialized = true
|
||||
this.playbackStarted = true
|
||||
|
||||
this.scheduleRequestIfNeeded()
|
||||
this.createRequest(this.metrics, 'play')
|
||||
}
|
||||
|
||||
private onPlaybackEnded(event) {
|
||||
this.unscheduleRequest()
|
||||
this.playbackStarted = false
|
||||
}
|
||||
|
||||
private onPlayerCanplay(event) {
|
||||
if (this.metrics == null) { return }
|
||||
|
||||
this.playbackInitialized = true
|
||||
|
||||
this.createRequest(this.metrics, 'update')
|
||||
this.scheduleRequestIfNeeded()
|
||||
}
|
||||
|
||||
private onPlaybackPaused(event) {
|
||||
if (this.metrics == null) { return }
|
||||
|
||||
this.scheduleRequestIfNeeded()
|
||||
this.createRequest(this.metrics, 'pause')
|
||||
}
|
||||
|
||||
private scheduleRequestIfNeeded() {
|
||||
if (this.scheduledRequest !== undefined) { return }
|
||||
this.scheduleRequest()
|
||||
}
|
||||
|
||||
private scheduleRequest() {
|
||||
this.unscheduleRequest()
|
||||
const metrics = this.metrics
|
||||
if (metrics == null) { return }
|
||||
this.scheduledRequest = TimerUtils.setInterval(
|
||||
() => { this.createRequest(metrics, 'periodic') },
|
||||
metrics * 1000,
|
||||
)
|
||||
}
|
||||
|
||||
private unscheduleRequest() {
|
||||
if (this.scheduledRequest == null) { return }
|
||||
clearInterval(this.scheduledRequest)
|
||||
this.scheduledRequest = null
|
||||
}
|
||||
|
||||
private createRequest(metrics: VokaCorePlayer.IMetrics, state: string) {
|
||||
const params = metrics.params || {}
|
||||
|
||||
let playerState = this.getPlayerState()
|
||||
switch (state) {
|
||||
case 'init':
|
||||
playerState = 'initialization'
|
||||
break
|
||||
case 'buffering':
|
||||
playerState = 'initial_buffering'
|
||||
break
|
||||
case 'play':
|
||||
playerState = 'playing'
|
||||
break
|
||||
case 'pause':
|
||||
playerState = 'paused'
|
||||
break
|
||||
case 'periodic':
|
||||
case 'update':
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
return this.defaultWatchSessionId
|
||||
}
|
||||
|
||||
private getPlayerState(): string {
|
||||
if (!this.playbackInitialized) { return 'initial_buffering' }
|
||||
if (!this.playbackStarted) { return 'paused' }
|
||||
if (this.player.paused()) { return 'paused' }
|
||||
// TODO!
|
||||
// if (player.getBufferingState()) { return 'freezed' }
|
||||
return 'playing'
|
||||
}
|
||||
if (state === 'update' && playerState === this.prevPlayerState) { return }
|
||||
|
||||
private updateTimeSpent() {
|
||||
if (this.timeSpent == null) {
|
||||
this.timeSpent = {
|
||||
initializing_player: 0,
|
||||
initializing_buffer: 0,
|
||||
playing: 0,
|
||||
paused: 0,
|
||||
buffering: 0
|
||||
this.updateTimeSpent()
|
||||
this.prevPlayerState = playerState
|
||||
|
||||
if (state != 'periodic') { return }
|
||||
|
||||
if (state == 'periodic') {
|
||||
this.timeout = metrics.interval * 2 * 1000
|
||||
} else {
|
||||
this.timeout = 20000
|
||||
}
|
||||
|
||||
const osVersion = BrowserUtils.getOSVersion()
|
||||
let deviceOS = osVersion.name.toLowerCase()
|
||||
switch (deviceOS) {
|
||||
case 'ios':
|
||||
case 'android':
|
||||
case 'windows':
|
||||
case 'macos':
|
||||
case 'linux':
|
||||
break
|
||||
default:
|
||||
deviceOS = 'other'
|
||||
break
|
||||
}
|
||||
|
||||
// TODO!
|
||||
let bandwidth = NaN//player.getNetworkBandwidth();
|
||||
if (isNaN(bandwidth)) {
|
||||
bandwidth = 0
|
||||
} else {
|
||||
bandwidth *= 1000
|
||||
}
|
||||
|
||||
const data = {
|
||||
application_id: params.application_id,
|
||||
application_version: params['application_version'],
|
||||
user_type: params['user_type'],
|
||||
user_id: params.user_id,
|
||||
device_id: BrowserUtils.deviceID,
|
||||
device_os: deviceOS,
|
||||
device_type: 'browser',
|
||||
device_player_type: 'native',
|
||||
application_session_id: GUIDUtils.sessionGUID,
|
||||
watch_session_id: this.getWatchSessionId(),
|
||||
resource_uid: params['resource_uid'],
|
||||
resource_type: params.resource_type,
|
||||
// TODO!
|
||||
// buffered_duration: Math.floor(player.getBufferLength() * 1000),
|
||||
bandwidth: Math.floor(bandwidth),
|
||||
player_state: playerState,
|
||||
time_spent: this.timeSpent,
|
||||
network_type: 'unknown'
|
||||
};
|
||||
|
||||
/*const request = this.httpClient.request(
|
||||
HTTPMethod.POST,
|
||||
this.getApiUrl(),
|
||||
null,
|
||||
data,
|
||||
{ timeout: timeout, withCredentials: true }
|
||||
)*/
|
||||
|
||||
this.timeSpent = null
|
||||
}
|
||||
|
||||
const currTime = DateUtils.getCurrTimeMS()
|
||||
if (this.prevStateTime == 0) {
|
||||
private getApiUrl(): string | null {
|
||||
if (this.metrics == null) { return null }
|
||||
|
||||
if (this.metrics.url != null) {
|
||||
return this.metrics.url
|
||||
}
|
||||
|
||||
return 'https://127.0.0.1/v2/player'
|
||||
}
|
||||
|
||||
private getWatchSessionId() {
|
||||
if (this.metrics != null) {
|
||||
const params = this.metrics.params
|
||||
if (params != null && params.watch_session_id != null) {
|
||||
return params.watch_session_id
|
||||
}
|
||||
}
|
||||
return this.defaultWatchSessionId
|
||||
}
|
||||
|
||||
private getPlayerState(): string {
|
||||
if (!this.playbackInitialized) { return 'initial_buffering' }
|
||||
if (!this.playbackStarted) { return 'paused' }
|
||||
if (this.player.paused()) { return 'paused' }
|
||||
// TODO!
|
||||
// if (player.getBufferingState()) { return 'freezed' }
|
||||
return 'playing'
|
||||
}
|
||||
|
||||
private updateTimeSpent() {
|
||||
if (this.timeSpent == null) {
|
||||
this.timeSpent = {
|
||||
initializing_player: 0,
|
||||
initializing_buffer: 0,
|
||||
playing: 0,
|
||||
paused: 0,
|
||||
buffering: 0
|
||||
}
|
||||
}
|
||||
|
||||
const currTime = DateUtils.getCurrTimeMS()
|
||||
if (this.prevStateTime == 0) {
|
||||
this.prevStateTime = currTime
|
||||
}
|
||||
|
||||
const diffTime = currTime - this.prevStateTime
|
||||
this.prevStateTime = currTime
|
||||
|
||||
let propName = null
|
||||
switch (this.prevPlayerState) {
|
||||
case 'initialization':
|
||||
propName = 'initializing_player'
|
||||
break
|
||||
case 'initial_buffering':
|
||||
propName = 'initializing_buffer'
|
||||
break
|
||||
case 'paused':
|
||||
propName = 'paused'
|
||||
break
|
||||
case 'freezed':
|
||||
propName = 'buffering'
|
||||
break
|
||||
case 'playing':
|
||||
propName = 'playing'
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
this.timeSpent[propName] += diffTime
|
||||
}
|
||||
|
||||
const diffTime = currTime - this.prevStateTime
|
||||
this.prevStateTime = currTime
|
||||
|
||||
let propName = null
|
||||
switch (this.prevPlayerState) {
|
||||
case 'initialization':
|
||||
propName = 'initializing_player'
|
||||
break
|
||||
case 'initial_buffering':
|
||||
propName = 'initializing_buffer'
|
||||
break
|
||||
case 'paused':
|
||||
propName = 'paused'
|
||||
break
|
||||
case 'freezed':
|
||||
propName = 'buffering'
|
||||
break
|
||||
case 'playing':
|
||||
propName = 'playing'
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
this.timeSpent[propName] += diffTime
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
videojs.registerPlugin('vokaMetricsPlugin', VokaMetricsPlugin)
|
||||
videojs.registerPlugin('vokaMetricsPlugin', VokaMetricsPlugin.Plugin)
|
||||
export default VokaMetricsPlugin
|
||||
+175
-170
@@ -20,205 +20,210 @@ import MenuItem from "@/components/menu/MenuItem"
|
||||
import SettingsMenuButton from "../components/control-bar/menu/SettingsMenuButton"
|
||||
import SettingsQualityMenu from "../components/control-bar/menu/SettingsQualityMenu"
|
||||
|
||||
const Plugin = videojs.getPlugin("plugin")
|
||||
namespace VokaQualityPlugin {
|
||||
|
||||
export class VokaQualityPlugin extends Plugin {
|
||||
constructor(player: VideoJsPlayer) {
|
||||
super(player)
|
||||
this.setupListeners(player)
|
||||
}
|
||||
const VideoPlugin = videojs.getPlugin("plugin")
|
||||
|
||||
addQualityMenuItem(menu: Component) {
|
||||
const qualityMenuItem = new MenuItem(this.player, {
|
||||
name: "QualityMenuItem",
|
||||
value:
|
||||
QualityMapper.getLabelByQualityType(
|
||||
QualityType.AUTO,
|
||||
PlayerOptionsContext.qualityLabelVariant
|
||||
) || this.player.localize("Auto"),
|
||||
label: "Quality",
|
||||
id: "menu-quality-item"
|
||||
})
|
||||
export class Plugin extends VideoPlugin {
|
||||
constructor(player: VideoJsPlayer) {
|
||||
super(player)
|
||||
this.setupListeners(player)
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
menu.addChild(qualityMenuItem, {}, 0)
|
||||
|
||||
const qualityParsedEvent = bus.subscribe(qualitiesParsed, () => {
|
||||
const qualityMenuItem = menu.getChild("QualityMenuItem")
|
||||
if (qualityMenuItem) {
|
||||
;(qualityMenuItem as MenuItem).valueLabel =
|
||||
addQualityMenuItem(menu: Component) {
|
||||
const qualityMenuItem = new MenuItem(this.player, {
|
||||
name: "QualityMenuItem",
|
||||
value:
|
||||
QualityMapper.getLabelByQualityType(
|
||||
QualityType.AUTO,
|
||||
PlayerOptionsContext.qualityLabelVariant
|
||||
) || this.player.localize("Auto")
|
||||
}
|
||||
|
||||
const qualityChangeEvent = bus.subscribe(qualityChange, ({ payload }) => {
|
||||
this.player.removeClass("vjs-quality-loader")
|
||||
if (PlayerOptionsContext.isAutoQuality) return
|
||||
if (qualityMenuItem) {
|
||||
qualityMenuItem.valueLabel = QualityMapper.getLabelByValue(
|
||||
payload.quality,
|
||||
PlayerOptionsContext.qualityLabelVariant
|
||||
) as string
|
||||
}
|
||||
) || this.player.localize("Auto"),
|
||||
label: "Quality",
|
||||
id: "menu-quality-item"
|
||||
})
|
||||
|
||||
const qualitySetEvent = bus.subscribe(qualitySet, ({ payload }) => {
|
||||
if (payload.quality !== Quality.AUTO) return
|
||||
qualityMenuItem.valueLabel =
|
||||
QualityMapper.getLabelByQualityType(
|
||||
QualityType.AUTO,
|
||||
PlayerOptionsContext.qualityLabelVariant
|
||||
) || this.player.localize("Auto")
|
||||
})
|
||||
// @ts-ignore
|
||||
menu.addChild(qualityMenuItem, {}, 0)
|
||||
|
||||
const qualityUISetEvent = bus.subscribe(qualityUISet, ({ payload }) => {
|
||||
if (PlayerOptionsContext.isAutoQuality) return
|
||||
const qualityParsedEvent = bus.subscribe(qualitiesParsed, () => {
|
||||
const qualityMenuItem = menu.getChild("QualityMenuItem")
|
||||
if (qualityMenuItem) {
|
||||
qualityMenuItem.valueLabel = QualityMapper.getLabelByValue(
|
||||
payload.quality,
|
||||
PlayerOptionsContext.qualityLabelVariant
|
||||
) as string
|
||||
;(qualityMenuItem as MenuItem).valueLabel =
|
||||
QualityMapper.getLabelByQualityType(
|
||||
QualityType.AUTO,
|
||||
PlayerOptionsContext.qualityLabelVariant
|
||||
) || this.player.localize("Auto")
|
||||
}
|
||||
|
||||
const qualityChangeEvent = bus.subscribe(qualityChange, ({ payload }) => {
|
||||
this.player.removeClass("vjs-quality-loader")
|
||||
if (PlayerOptionsContext.isAutoQuality) return
|
||||
if (qualityMenuItem) {
|
||||
qualityMenuItem.valueLabel = QualityMapper.getLabelByValue(
|
||||
payload.quality,
|
||||
PlayerOptionsContext.qualityLabelVariant
|
||||
) as string
|
||||
}
|
||||
})
|
||||
|
||||
const qualitySetEvent = bus.subscribe(qualitySet, ({ payload }) => {
|
||||
if (payload.quality !== Quality.AUTO) return
|
||||
qualityMenuItem.valueLabel =
|
||||
QualityMapper.getLabelByQualityType(
|
||||
QualityType.AUTO,
|
||||
PlayerOptionsContext.qualityLabelVariant
|
||||
) || this.player.localize("Auto")
|
||||
})
|
||||
|
||||
const qualityUISetEvent = bus.subscribe(qualityUISet, ({ payload }) => {
|
||||
if (PlayerOptionsContext.isAutoQuality) return
|
||||
if (qualityMenuItem) {
|
||||
qualityMenuItem.valueLabel = QualityMapper.getLabelByValue(
|
||||
payload.quality,
|
||||
PlayerOptionsContext.qualityLabelVariant
|
||||
) as string
|
||||
}
|
||||
})
|
||||
|
||||
this.player.one("playerreset", () => {
|
||||
qualitySetEvent()
|
||||
qualityChangeEvent()
|
||||
qualityUISetEvent()
|
||||
})
|
||||
})
|
||||
|
||||
this.player.one("playerreset", () => {
|
||||
qualitySetEvent()
|
||||
qualityChangeEvent()
|
||||
qualityUISetEvent()
|
||||
qualityParsedEvent()
|
||||
})
|
||||
})
|
||||
|
||||
this.player.one("playerreset", () => {
|
||||
qualityParsedEvent()
|
||||
})
|
||||
}
|
||||
|
||||
public handleSettingsButton(menuButton: Component) {
|
||||
bus.subscribe(qualityChange, ({ payload }) => {
|
||||
this.processMenuButtonBadges(menuButton, payload.quality)
|
||||
})
|
||||
|
||||
bus.subscribe(qualitySet, ({ payload }) => {
|
||||
if (payload.quality === Quality.AUTO) {
|
||||
this.removeSettingsButtonBadges(menuButton)
|
||||
}
|
||||
})
|
||||
|
||||
bus.subscribe(qualityUISet, ({ payload }) => {
|
||||
this.processMenuButtonBadges(menuButton, payload.quality)
|
||||
})
|
||||
}
|
||||
|
||||
private processMenuButtonBadges(button: Component, quality: Quality) {
|
||||
this.removeSettingsButtonBadges(button)
|
||||
|
||||
if (PlayerOptionsContext.isAutoQuality) return
|
||||
|
||||
if (quality === Quality.P720) {
|
||||
button.addClass("vjs-menu-button--quality-hd")
|
||||
return
|
||||
}
|
||||
if (quality === Quality.P1080) {
|
||||
button.addClass("vjs-menu-button--quality-fhd")
|
||||
return
|
||||
|
||||
public handleSettingsButton(menuButton: Component) {
|
||||
bus.subscribe(qualityChange, ({ payload }) => {
|
||||
this.processMenuButtonBadges(menuButton, payload.quality)
|
||||
})
|
||||
|
||||
bus.subscribe(qualitySet, ({ payload }) => {
|
||||
if (payload.quality === Quality.AUTO) {
|
||||
this.removeSettingsButtonBadges(menuButton)
|
||||
}
|
||||
})
|
||||
|
||||
bus.subscribe(qualityUISet, ({ payload }) => {
|
||||
this.processMenuButtonBadges(menuButton, payload.quality)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private removeSettingsButtonBadges(button: Component) {
|
||||
button.removeClass("vjs-menu-button--quality-hd")
|
||||
button.removeClass("vjs-menu-button--quality-fhd")
|
||||
}
|
||||
private processMenuButtonBadges(button: Component, quality: Quality) {
|
||||
this.removeSettingsButtonBadges(button)
|
||||
|
||||
private setupListeners(player: VideoJsPlayer) {
|
||||
bus.subscribe(qualityChange, (data) => {
|
||||
if (PlayerOptionsContext.expectedQuality) {
|
||||
if (data.payload.quality === PlayerOptionsContext.expectedQuality) {
|
||||
bus.publish(
|
||||
contextOptionsUpdated({
|
||||
expectedQuality: null,
|
||||
isAutoQuality: false
|
||||
})
|
||||
)
|
||||
bus.publish(
|
||||
qualityUISet({
|
||||
quality: data.payload.quality
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
if (PlayerOptionsContext.isAutoQuality) return
|
||||
|
||||
bus.publish(
|
||||
contextOptionsUpdated({
|
||||
quality: data.payload.quality
|
||||
})
|
||||
)
|
||||
})
|
||||
bus.subscribe(qualitySet, (data) => {
|
||||
// Если текущее качество равно проставляемому
|
||||
if (PlayerOptionsContext.quality === data.payload.quality) {
|
||||
// Если текущее качество не авто, то не обрабатываем клик
|
||||
if (!PlayerOptionsContext.isAutoQuality) {
|
||||
return
|
||||
} else {
|
||||
// Если авто, обновляем UI
|
||||
bus.publish(
|
||||
contextOptionsUpdated({
|
||||
isAutoQuality: false
|
||||
})
|
||||
)
|
||||
bus.publish(
|
||||
qualityUISet({
|
||||
quality: data.payload.quality
|
||||
})
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (data.payload.quality !== Quality.AUTO) {
|
||||
player.addClass("vjs-quality-loader")
|
||||
}
|
||||
|
||||
if (data.payload.noAutoChange) {
|
||||
bus.publish(
|
||||
contextOptionsUpdated({
|
||||
expectedQuality: data.payload.quality
|
||||
})
|
||||
)
|
||||
if (quality === Quality.P720) {
|
||||
button.addClass("vjs-menu-button--quality-hd")
|
||||
return
|
||||
}
|
||||
if (quality === Quality.P1080) {
|
||||
button.addClass("vjs-menu-button--quality-fhd")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
bus.publish(
|
||||
contextOptionsUpdated({
|
||||
isAutoQuality: Quality.AUTO === data.payload.quality
|
||||
})
|
||||
)
|
||||
private removeSettingsButtonBadges(button: Component) {
|
||||
button.removeClass("vjs-menu-button--quality-hd")
|
||||
button.removeClass("vjs-menu-button--quality-fhd")
|
||||
}
|
||||
|
||||
private setupListeners(player: VideoJsPlayer) {
|
||||
bus.subscribe(qualityChange, (data) => {
|
||||
if (PlayerOptionsContext.expectedQuality) {
|
||||
if (data.payload.quality === PlayerOptionsContext.expectedQuality) {
|
||||
bus.publish(
|
||||
contextOptionsUpdated({
|
||||
expectedQuality: null,
|
||||
isAutoQuality: false
|
||||
})
|
||||
)
|
||||
bus.publish(
|
||||
qualityUISet({
|
||||
quality: data.payload.quality
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (data.payload.withUIUpdate) {
|
||||
bus.publish(
|
||||
qualityUISet({
|
||||
contextOptionsUpdated({
|
||||
quality: data.payload.quality
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
bus.subscribe(qualitySet, (data) => {
|
||||
// Если текущее качество равно проставляемому
|
||||
if (PlayerOptionsContext.quality === data.payload.quality) {
|
||||
// Если текущее качество не авто, то не обрабатываем клик
|
||||
if (!PlayerOptionsContext.isAutoQuality) {
|
||||
return
|
||||
} else {
|
||||
// Если авто, обновляем UI
|
||||
bus.publish(
|
||||
contextOptionsUpdated({
|
||||
isAutoQuality: false
|
||||
})
|
||||
)
|
||||
bus.publish(
|
||||
qualityUISet({
|
||||
quality: data.payload.quality
|
||||
})
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
bus.subscribe(showQualityLoader, ({ payload }) => {
|
||||
payload.value
|
||||
? player.addClass("vjs-quality-loader")
|
||||
: player.removeClass("vjs-quality-loader")
|
||||
})
|
||||
player.on("playerreset", () => {
|
||||
bus.publish(
|
||||
qualitySet({
|
||||
quality: -1,
|
||||
forced: true
|
||||
})
|
||||
)
|
||||
})
|
||||
if (data.payload.quality !== Quality.AUTO) {
|
||||
player.addClass("vjs-quality-loader")
|
||||
}
|
||||
|
||||
if (data.payload.noAutoChange) {
|
||||
bus.publish(
|
||||
contextOptionsUpdated({
|
||||
expectedQuality: data.payload.quality
|
||||
})
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
bus.publish(
|
||||
contextOptionsUpdated({
|
||||
isAutoQuality: Quality.AUTO === data.payload.quality
|
||||
})
|
||||
)
|
||||
|
||||
if (data.payload.withUIUpdate) {
|
||||
bus.publish(
|
||||
qualityUISet({
|
||||
quality: data.payload.quality
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
bus.subscribe(showQualityLoader, ({ payload }) => {
|
||||
payload.value
|
||||
? player.addClass("vjs-quality-loader")
|
||||
: player.removeClass("vjs-quality-loader")
|
||||
})
|
||||
player.on("playerreset", () => {
|
||||
bus.publish(
|
||||
qualitySet({
|
||||
quality: -1,
|
||||
forced: true
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
videojs.registerPlugin("vokaQualityPlugin", VokaQualityPlugin)
|
||||
videojs.registerPlugin("vokaQualityPlugin", VokaQualityPlugin.Plugin)
|
||||
export default VokaQualityPlugin
|
||||
+54
-7
@@ -46,8 +46,7 @@ interface IMetricsParams {
|
||||
}
|
||||
|
||||
interface IMetrics {
|
||||
apiUrl: string | null
|
||||
apiHost: string | null
|
||||
url: string
|
||||
params: IMetricsParams | null
|
||||
interval: number
|
||||
}
|
||||
@@ -86,7 +85,7 @@ namespace VokaOptions {
|
||||
apiConfig: IAPIConfig
|
||||
uiConfig: IUIConfig | null
|
||||
globalOpts: IGlobalOptions | null
|
||||
streamOpts: IStreamOptions | null
|
||||
streamOpts: StreamOptions.IStream | null
|
||||
codecs: ICodecs
|
||||
tweaks: ITweaks
|
||||
controls: UIControls.IControls
|
||||
@@ -136,12 +135,60 @@ namespace VokaOptions {
|
||||
uiLanguage: string
|
||||
}
|
||||
|
||||
export interface IStreamOptions {
|
||||
autoplay: boolean
|
||||
export namespace StreamOptions {
|
||||
|
||||
export interface IDRMWideVine {
|
||||
proxy_url: string | null
|
||||
}
|
||||
|
||||
export interface IDRMPlayReady {
|
||||
la_url: string | null
|
||||
}
|
||||
|
||||
export interface IDRMFairplay {
|
||||
certificate_url: string,
|
||||
ksm_url: string,
|
||||
ksm_protocol: string,
|
||||
add_no_cache_headers: boolean
|
||||
}
|
||||
|
||||
export interface IDRMConfig {
|
||||
widevine: IDRMWideVine | null
|
||||
fairplay: IDRMFairplay | null
|
||||
playready: IDRMPlayReady | null
|
||||
}
|
||||
|
||||
export interface IHeartbeat {
|
||||
url: string | null
|
||||
interval: number
|
||||
version: number
|
||||
}
|
||||
|
||||
export interface IMetricsParams {
|
||||
application_id: string | null
|
||||
application_version: string | null
|
||||
user_type: string | null
|
||||
user_id: string | null
|
||||
resource_uid: string | null
|
||||
resource_type: string | null
|
||||
watch_session_id: string | null
|
||||
}
|
||||
|
||||
export interface IMetrics {
|
||||
apiHost: string | null
|
||||
apiUrl: string | null
|
||||
interval: number
|
||||
params: IMetricsParams | null
|
||||
}
|
||||
|
||||
export interface IStream {
|
||||
autoplay: boolean
|
||||
drmConfig: IDRMConfig | null
|
||||
metrics: IMetrics | null
|
||||
heartbeat: IHeartbeat | null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export { IOSVersion, IContent, IContextUpdated, IHeartbeat, IMetricsParams, IMetrics, ICaption,
|
||||
VokaOptions, Error, VokaErrorMessages }
|
||||
@@ -69,10 +69,43 @@ export class VokaPlayerImpl implements IVokaPlayer {
|
||||
|
||||
private load(content: IResult, player: CorePlayer) {
|
||||
|
||||
const drmConfig = { type: VokaCorePlayer.DRMType.NONE } as VokaCorePlayer.IDRMConfig
|
||||
if (content.drmConfig != null) {
|
||||
switch (content.drmConfig.type) {
|
||||
case VokaApi.DRMType.PLAYREADY:
|
||||
drmConfig.type = VokaCorePlayer.DRMType.PLAYREADY
|
||||
break
|
||||
case VokaApi.DRMType.FAIRPLAY:
|
||||
drmConfig.type = VokaCorePlayer.DRMType.FAIRPLAY
|
||||
break
|
||||
case VokaApi.DRMType.WIDEVINE:
|
||||
drmConfig.type = VokaCorePlayer.DRMType.WIDEVINE
|
||||
break
|
||||
}
|
||||
drmConfig.certificateUrl = content.drmConfig.certificateUrl
|
||||
drmConfig.licenseUrl = content.drmConfig.keyServerUrl
|
||||
} else if (content.streamOptions?.drmConfig != null) {
|
||||
const config = content.streamOptions?.drmConfig
|
||||
if (config?.fairplay != null) {
|
||||
drmConfig.type = VokaCorePlayer.DRMType.FAIRPLAY
|
||||
drmConfig.certificateUrl = config.fairplay.certificate_url
|
||||
drmConfig.licenseUrl = config.fairplay.ksm_url
|
||||
} else if (config?.widevine != null) {
|
||||
drmConfig.type = VokaCorePlayer.DRMType.WIDEVINE
|
||||
drmConfig.certificateUrl = config.widevine.proxy_url || ""
|
||||
} else if (config?.playready != null) {
|
||||
drmConfig.type = VokaCorePlayer.DRMType.PLAYREADY
|
||||
drmConfig.certificateUrl = config.playready.la_url || ""
|
||||
}
|
||||
}
|
||||
|
||||
const iContent = {
|
||||
url: content.url,
|
||||
type: VokaContentType.HLS,
|
||||
type: VokaPlayerImpl.contentType(content.url, drmConfig),
|
||||
drmConfig: drmConfig.type != VokaCorePlayer.DRMType.NONE ? drmConfig : null,
|
||||
subtitlesUrl: content.subtitlesUrl,
|
||||
metrics: content.metrics || content.streamOptions?.metrics,
|
||||
heartbeat: content.streamOptions?.heartbeat
|
||||
} as VokaCorePlayer.IContent
|
||||
|
||||
player.load(iContent)
|
||||
@@ -81,13 +114,13 @@ export class VokaPlayerImpl implements IVokaPlayer {
|
||||
private async initialize(player: CorePlayer, features: SupportedCodecs.ISelectProtocolResult): Promise<boolean> {
|
||||
if (!this.options.features.api) { return false }
|
||||
|
||||
const content = await this.fetchContent(this.options, features)
|
||||
const content = await VokaPlayerImpl.fetchContent(this.options, features)
|
||||
this.load(content, player)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private async fetchContent(options: VokaOptions.IOptions, features: SupportedCodecs.ISelectProtocolResult) {
|
||||
private static async fetchContent(options: VokaOptions.IOptions, features: SupportedCodecs.ISelectProtocolResult) {
|
||||
const apiOptions = {
|
||||
apiHost: options.apiConfig.apiHost,
|
||||
movieId: options.apiConfig.movieId,
|
||||
@@ -115,7 +148,7 @@ export class VokaPlayerImpl implements IVokaPlayer {
|
||||
default: return 'spbtvcas'
|
||||
}
|
||||
},
|
||||
getPlayerVersion: () => `${VokaPlayerImpl.version}.${VokaPlayerImpl.build}`,
|
||||
getPlayerVersion: () => `${this.version}.${this.build}`,
|
||||
getBrowserName: () => BrowserUtils.browserInfo().name
|
||||
} as VokaApi.ISystemCapability
|
||||
|
||||
@@ -123,6 +156,25 @@ export class VokaPlayerImpl implements IVokaPlayer {
|
||||
return api.load()
|
||||
}
|
||||
|
||||
private static contentType(url: string, drmConfig: VokaCorePlayer.IDRMConfig): VokaContentType {
|
||||
const value = url.toLowerCase()
|
||||
if (value.indexOf('m3u8') > -1) {
|
||||
return drmConfig.type == VokaCorePlayer.DRMType.FAIRPLAY
|
||||
? VokaContentType.FAIRPLAY
|
||||
: VokaContentType.HLS
|
||||
}
|
||||
if (value.indexOf('dash') > -1 || value.indexOf('mpd') > -1) {
|
||||
if (drmConfig.type == VokaCorePlayer.DRMType.WIDEVINE) {
|
||||
return VokaContentType.WIDEVINE
|
||||
}
|
||||
if (drmConfig.type == VokaCorePlayer.DRMType.PLAYREADY) {
|
||||
return VokaContentType.PLAYREADY
|
||||
}
|
||||
return VokaContentType.DASH
|
||||
}
|
||||
return VokaContentType.MP4
|
||||
}
|
||||
|
||||
// MARK: - IVokaPlayer implementation
|
||||
|
||||
public afterInitialize(callback: () => void) {
|
||||
@@ -159,8 +211,17 @@ export class VokaPlayerImpl implements IVokaPlayer {
|
||||
return features.mseCodecs
|
||||
}
|
||||
|
||||
public attachSource(url: string, options: VokaOptions.IStreamOptions): void {
|
||||
public attachSource(
|
||||
url: string,
|
||||
options: VokaOptions.StreamOptions.IStream
|
||||
): void {
|
||||
|
||||
|
||||
|
||||
//private load(content: IResult, player: CorePlayer) {
|
||||
|
||||
// TODO!!!!
|
||||
// REQUIRE CONVERT TO IContent protocol!
|
||||
}
|
||||
|
||||
public getPaused(): boolean {
|
||||
@@ -352,12 +413,12 @@ export class VokaPlayerImpl implements IVokaPlayer {
|
||||
}
|
||||
} as VokaCorePlayer.IContent*/
|
||||
|
||||
// Dash WideWine DRM
|
||||
// Dash WIDEVINE DRM
|
||||
/*const iContent = {
|
||||
url: "https://media.axprod.net/TestVectors/Cmaf/protected_1080p_h264_cbcs/manifest.mpd",
|
||||
type: VokaContentType.WIDEVINE,
|
||||
drmConfig: {
|
||||
type: VokaCorePlayer.DRMType.WIDEWINE,
|
||||
type: VokaCorePlayer.DRMType.WIDEVINE,
|
||||
certificateUrl: "https://drm-widevine-licensing.axtest.net/AcquireLicense",
|
||||
headers: {
|
||||
"X-AxDRM-Message": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJ2ZXJzaW9uIjogMSwKICAiY29tX2tleV9pZCI6ICI2OWU1NDA4OC1lOWUwLTQ1MzAtOGMxYS0xZWI2ZGNkMGQxNGUiLAogICJtZXNzYWdlIjogewogICAgInR5cGUiOiAiZW50aXRsZW1lbnRfbWVzc2FnZSIsCiAgICAidmVyc2lvbiI6IDIsCiAgICAibGljZW5zZSI6IHsKICAgICAgImFsbG93X3BlcnNpc3RlbmNlIjogdHJ1ZQogICAgfSwKICAgICJjb250ZW50X2tleXNfc291cmNlIjogewogICAgICAiaW5saW5lIjogWwogICAgICAgIHsKICAgICAgICAgICJpZCI6ICIzMDJmODBkZC00MTFlLTQ4ODYtYmNhNS1iYjFmODAxOGEwMjQiLAogICAgICAgICAgImVuY3J5cHRlZF9rZXkiOiAicm9LQWcwdDdKaTFpNDNmd3YremZ0UT09IiwKICAgICAgICAgICJ1c2FnZV9wb2xpY3kiOiAiUG9saWN5IEEiCiAgICAgICAgfQogICAgICBdCiAgICB9LAogICAgImNvbnRlbnRfa2V5X3VzYWdlX3BvbGljaWVzIjogWwogICAgICB7CiAgICAgICAgIm5hbWUiOiAiUG9saWN5IEEiLAogICAgICAgICJwbGF5cmVhZHkiOiB7CiAgICAgICAgICAibWluX2RldmljZV9zZWN1cml0eV9sZXZlbCI6IDE1MCwKICAgICAgICAgICJwbGF5X2VuYWJsZXJzIjogWwogICAgICAgICAgICAiNzg2NjI3RDgtQzJBNi00NEJFLThGODgtMDhBRTI1NUIwMUE3IgogICAgICAgICAgXQogICAgICAgIH0KICAgICAgfQogICAgXQogIH0KfQ._NfhLVY7S6k8TJDWPeMPhUawhympnrk6WAZHOVjER6M",
|
||||
|
||||
@@ -55,7 +55,8 @@ namespace VokaApi {
|
||||
|
||||
export enum DRMType {
|
||||
FAIRPLAY = 'fairplay',
|
||||
PLAYREADY = 'playready'
|
||||
PLAYREADY = 'playready',
|
||||
WIDEVINE = 'widevine',
|
||||
}
|
||||
|
||||
export interface IDRMConfig {
|
||||
@@ -69,7 +70,7 @@ namespace VokaApi {
|
||||
subtitlesUrl: string | null
|
||||
drmConfig: IDRMConfig | null
|
||||
metrics: IMetrics | null,
|
||||
streamOptions: VokaOptions.IStreamOptions | null
|
||||
streamOptions: VokaOptions.StreamOptions.IStream | null
|
||||
}
|
||||
|
||||
export class Api {
|
||||
@@ -306,7 +307,7 @@ namespace VokaApi {
|
||||
'resource_type',
|
||||
'watch_session_id'
|
||||
].forEach((item) => {
|
||||
if ((item in params.additional_parameters) && typeof params.additional_parameters[item] !== 'string') {
|
||||
if ((item in params.additional_parameters) && typeof params.additional_parameters[item] == 'string') {
|
||||
metrics.params[item] = params.additional_parameters[item]
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user