Feature/63538 voka api

This commit is contained in:
Юрий Шикин
2025-05-28 13:07:02 +00:00
parent ca02852a4d
commit f7267e8b90
23 changed files with 1024 additions and 463 deletions
+4 -1
View File
@@ -1,6 +1,9 @@
{
"port": 8080,
"server": {
"baseDir": "./demo"
"baseDir": "./demo",
"routes": {
"/v1/devices.json": "v1/devices.json"
}
}
}
+38
View File
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script language="JavaScript" src="./distribution/vokaPlayer.global.js"></script>
</head>
<body>
<h1>Hello world</h1>
<div id="my-video"/>
<script language="JavaScript">
spbtvplayer('my-video', {
features: {
api: true,
drm: false,
metrics: true
},
apiConfig: {
channelId: 'mp4-0aec531fc015',
clientId: null,
movieId: null,
episodeId: null,
newsId: null,
apiHost: 'localhost:8080',
},
uiConfig: {
initAsLive: true
},
globalOpts: {
uiLanguage: 'ru'
},
streamOpts: {
autoplay: true
}
});
</script>
</body>
</html>
+17 -3
View File
@@ -9,15 +9,20 @@
<h1>Hello world</h1>
<div id="my-video"/>
<script language="JavaScript">
spbtvplayer('my-video', {
var player = spbtvplayer('my-video', {
features: {
api: true,
drm: false,
metrics: true
},
apiConfig: {
channelId: '998f5396-c9dd-4a1e-82c7-0aec531fc015',
urlGetParams: 'minheight=400'
channelId: 'hls-998f5396-c9dd-4a1e-82c7-0aec531fc015',
urlGetParams: 'minheight=400',
clientId: null,
movieId: null,
episodeId: null,
newsId: null,
apiHost: 'localhost:8080',
},
uiConfig: {
initAsLive: true
@@ -29,6 +34,15 @@
autoplay: true
}
});
player.afterInitialize(() => {
console.log("Inited 1")
})
player.afterInitialize(() => {
console.log("Inited 2")
})
player.afterInitialize(() => {
console.log("Inited 3")
})
</script>
</body>
</html>
+38
View File
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script language="JavaScript" src="./distribution/vokaPlayer.global.js"></script>
</head>
<body>
<h1>Hello world</h1>
<div id="my-video"/>
<script language="JavaScript">
spbtvplayer('my-video', {
features: {
api: true,
drm: false,
metrics: true
},
apiConfig: {
channelId: 'mp4-0aec531fc015',
clientId: null,
movieId: null,
episodeId: null,
newsId: null,
apiHost: 'localhost:8080',
},
uiConfig: {
initAsLive: true
},
globalOpts: {
uiLanguage: 'ru'
},
streamOpts: {
autoplay: true
}
});
</script>
</body>
</html>
@@ -0,0 +1,6 @@
{
"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"
}
}
@@ -0,0 +1,6 @@
{
"data": {
"url": "https://vjs.zencdn.net/v/oceans.mp4",
"subtitles": "https://raw.githubusercontent.com/videojs/video.js/c7298d40a4632a6e9dfcd5a2f5cc3bbe92a78744/docs/examples/elephantsdream/captions.ru.vtt"
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"data": {
"device_token": "SOME_DEVICE_TOKEN"
}
}
+1 -1
View File
@@ -355,7 +355,7 @@ export default class VokaDash {
}
},
debug: {
logLevel: options.debug.playerLogs ? Debug.LOG_LEVEL_DEBUG : Debug.LOG_LEVEL_NONE
logLevel: (options.debug != undefined && options.debug.playerLogs) ? Debug.LOG_LEVEL_DEBUG : Debug.LOG_LEVEL_NONE
}
})
}
+2 -2
View File
@@ -1,6 +1,6 @@
import { createEventDefinition } from 'ts-bus'
import { Quality } from '@/public/models/ILoadOptions'
import { QualityData } from '../player/VokaPlayerCore'
import VokaCorePlayer from '../player/VokaCorePlayer'
import {
VokaInternalErrorComponent,
VokaInternalErrorData,
@@ -17,7 +17,7 @@ export type ErrorEventData = {
// Quality
export const qualitiesParsed = createEventDefinition<{
qualities: QualityData[]
qualities: VokaCorePlayer.QualityData[]
}>()('quality.parsed')
export const qualityChange = createEventDefinition<{
+385
View File
@@ -0,0 +1,385 @@
import videojs, { num } from 'video.js'
import Player from 'video.js/dist/types/player'
import { EventBus } from 'ts-bus'
import { VokaPlayerEvent} from '@/public/IVokaPlayer'
import { AutoplayChecker } from '@/internal/utils/AutoplayChecker'
import { Quality } from '@/public/models/ILoadOptions'
import * as languages from '@/languages.json'
import '@/plugins/VokaKeyboardPlugin'
import '@/plugins/VokaHeartbeatPlugin'
import '@/plugins/VokaMetricsPlugin'
import '@/plugins/VokaMagicRemotePlugin'
import '@/components/Skin'
import { VokaContentType } from '@/public/models/VokaContentType'
import VokaWebOSTech from '@/internal/player/native/webos/tech/VokaWebOSTech'
import VokaTizenTech from '@/internal/player/native/tizen/tech/VokaTizenTech'
import VokaAppleTech from '@/internal/player/native/apple/tech/VokaAppleTech'
import VokaMp4Tech from '@/internal/player/native/mp4/tech/VokaMp4Tech'
import VokaHlsTech from '@/internal/player/native/hls/tech/VokaHlsTech'
import VokaDashTech from '@/internal/player/native/dash/tech/VokaDashTech'
import VokaEmptyTech from '@/internal/player/native/empty/VokaEmptyTech'
import { Promise } from 'es6-promise'
namespace VokaCorePlayer {
type VideoJsPlayerOptions = Parameters<typeof videojs>[1]
type PlayerReadyHandler = (player: CorePlayer) => void
const playbackRates = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]
const Dom = videojs.dom
const DivID = 'vokaPlayerVideoTag'
export type QualityData = {
index: number
bitrate: number
quality: Quality
label?: string
}
export interface IMetrics {
url: string
interval: number | null
params: { [key: string]: string }
}
export enum DRMType {
FAIRPLAY = 'fairplay',
PLAYREADY = 'playready',
WIDEWINE = 'widewine',
}
export interface IDRMConfig {
type: DRMType
certificateUrl: string // certificate or serverURL
licenseUrl: string | null, // licenseUrl
headers: [any] | null
}
export interface IContent {
url: string,
type: VokaContentType,
drmConfig: IDRMConfig | null
subtitlesUrl: string | null
metrics: IMetrics | null
}
export class CorePlayer {
private player!: Player
private autoPlaySupported!: boolean
private readonly stateEmitter: EventBus
// Constructor
constructor(element: HTMLElement, callback?: PlayerReadyHandler) {
const readyCallback = typeof callback === 'undefined' ? null : callback
this.stateEmitter = new EventBus()
/*this.stateEmitter.subscribe(
playerStateChange,
(data) => (this.state_ = data.payload.value)
)*/
AutoplayChecker.isAutoplaySupported((supported) => {
this.setupPlayer(element, supported, readyCallback)
})
}
// MARK: - Public
public get isPaused(): boolean { return this.player.paused() }
public get currentTime(): number { return this.player.currentTime() || 0 }
public get duration(): number { return this.player.duration() || 0 }
public get volume(): number { return this.player.volume() || 0 }
public setVolume(value: number) { this.player.volume(value) }
public get bufferLength(): number { return this.player.bufferedPercent() }
public get isBuffering(): boolean { return false }
public seek(seconds: number): void { this.player.currentTime(seconds) }
public mute(on: boolean) { this.player.muted(on) }
public play(): Promise<any> | undefined { return this.player.play() }
public pause() { return this.player.pause() }
public stop() { return this.player.pause() }
public get isMuted(): boolean { return this.player.muted() || false }
public get isLive(): boolean {
const duration = this.player.duration()
if (duration == undefined) { return true }
if (isNaN(duration)) { return false }
if (isFinite(duration)) { return false }
return true
}
public async load(content: IContent): Promise<void> {
let playableContent = null
const drm = content.drmConfig
switch(content.type) {
case VokaContentType.HLS:
playableContent = {
type: "application/x-mpegURL",
}
break
case VokaContentType.MP4:
playableContent = {
type: "video/mp4",
}
break
case VokaContentType.DASH:
playableContent = {
type: "application/dash+xml",
}
break
case VokaContentType.WIDEVINE:
if (drm != null && drm.type == DRMType.WIDEWINE) {
playableContent = {
type: "application/dash+xml",
keySystemOptions: [{
name: "com.widevine.alpha",
options: {
serverURL: drm.certificateUrl,
httpRequestHeaders: drm.headers,
priority: 0,
}
}]
}
}
break
case VokaContentType.PLAYREADY:
if (drm != null && drm.type == DRMType.PLAYREADY) {
playableContent = {
type: "application/dash+xml",
keySystemOptions: [{
name: "com.microsoft.playready",
options: {
serverURL: drm.certificateUrl,
httpRequestHeaders: drm.headers,
priority: 0,
}
}]
}
}
break
case VokaContentType.FAIRPLAY:
if (drm != null && drm.type == DRMType.FAIRPLAY) {
playableContent = {
type: "application/x-mpegURL",
protection: {
keySystem: "com.apple.fps.1_0",
certificateUrl: drm.certificateUrl,
licenseUrl: drm.licenseUrl,
},
}
}
break
case VokaContentType.AES:
playableContent = {
type: "application/x-mpegURL",
}
break
}
if (playableContent != null) {
playableContent['src'] = content.url
playableContent['content'] = content.type
playableContent['metrics'] = content.metrics
playableContent['subtitlesUrl'] = content.subtitlesUrl
this.player.src(playableContent)
return Promise.resolve()
}
return Promise.reject()
}
// MARK: - Private
private setupPlayer(
element: HTMLElement,
autoPlaySupported: boolean,
callback: PlayerReadyHandler | null
) {
this.autoPlaySupported = autoPlaySupported
// Create main container for player.
const playerContainer = Dom.createEl(
'video',
{ id: `${DivID}_${element.id}_` },
{}
)
element.appendChild(playerContainer)
videojs.log.level('debug')
const options = this.initOptions()
const player = videojs(playerContainer, options, () => {
if (callback != null) {
callback(this)
}
this.player.play()
// try load content just right after complete initialization.
/*this.stateEmitter.publish(
playerStateChange({ value: Initialized })
)*/
})
player.addClass('video-js')
/*if (userAgent.getDevice().type === DEVICE.MOBILE) {
player.addClass('vjs-mobile')
}*/
// Sign to events.
this.setupAttachListeners(player)
this.player = player
}
private initOptions(): VideoJsPlayerOptions {
// Required player's plugins.
const plugins: Record<string, any> = { }
//plugins.vokaQualityPlugin = {}
//plugins.vokaStatisticsPlugin = {}
//plugins.vokaAdvertisementPlugin = {}
//plugins.vokaCaptionsPlugin = {}
// plugins.vokaKeyboardPlugin = {
// skip: {
// forward: 5,
// backward: 5,
// },
// }
plugins.vokaMagicRemotePlugin = {
skip: {
forward: 5,
backward: 5,
},
}
plugins.vokaHeartbeatPlugin = {} // Хартбит Player.Heartbeat
plugins.vokaMetricsPlugin = {} // Метрики Player.Metrics
const childrenComponents = [
// Non-visual component, not in UI layer list.
'mediaLoader' // Required loader, that load tech list!
]
const skinChildren = [
'Skin',
'resizeManager',
'LoadingSpinner',
'PosterImage',
'RestrictionBox',
'liveTracker',
]
childrenComponents.push(...skinChildren)
// Settings for player.
return {
techOrder: [
VokaWebOSTech.TECH_NAME,
VokaTizenTech.TECH_NAME,
VokaAppleTech.TECH_NAME,
VokaMp4Tech.TECH_NAME,
VokaHlsTech.TECH_NAME,
VokaDashTech.TECH_NAME,
VokaEmptyTech.TECH_NAME
], // Order for teches important.
plugins, // Attached plugins for player.
languages,
language: 'ru',
children: childrenComponents,
poster: undefined,
responsive: true,
breakpoints: {
tiny: undefined,
xsmall: undefined,
small: 559,
medium: 1002,
large: undefined,
xlarge: undefined,
huge: Infinity
},
playbackRates,
tracks: [
{
kind: "subtitles",
src: "https://raw.githubusercontent.com/videojs/video.js/c7298d40a4632a6e9dfcd5a2f5cc3bbe92a78744/docs/examples/elephantsdream/captions.ru.vtt",
srclang: 'ru',
label: 'russian',
},
{
kind: "subtitles",
src: "https://raw.githubusercontent.com/videojs/video.js/c7298d40a4632a6e9dfcd5a2f5cc3bbe92a78744/docs/examples/shared/example-captions.vtt",
srclang: 'en',
label: 'english',
}
],
qualities: [
{
res: 480,
label: "480p",
},
{
res: 720,
label: "720p",
},
{
res: 1080,
label: "1080p",
}
],
audio: [
{
res: "ru",
label: "Русский",
},
{
res: "en",
label: "Анлийский",
},
],
skip: {
forward: 5,
backward: 5,
},
controls: {
play: true,
replay: true,
volume: {
inline: true,
},
progress: true,
zoom: true,
fullscreen: true,
},
selection: true,
previewPopup: {
imageCallback: (percent: number) => {
return ''
// timeline from voka (for test)
// return `https://streaming.voka.tv/vod_preview/velcom/4W17sRxFu8eCAps3kdTxJrFk7d46DNmum_320x180.jpeg?preview_pos=${percent}`;
},
},
enableDocumentPictureInPicture: true,
}
}
private setupAttachListeners(player: Player) {
// Добавляем листенеры здесь
player.on('loadstart', () => {
// TODO: удалить, после подключения субтитров не из внешенго источника
document.querySelector('.vjs-tech')?.setAttribute('crossOrigin', 'anonymous')
})
}
}
}
export default VokaCorePlayer
-294
View File
@@ -1,294 +0,0 @@
import videojs from 'video.js'
import Player from 'video.js/dist/types/player'
import { EventBus } from 'ts-bus'
import { VokaPlayerEvent} from '@/public/IVokaPlayer'
import { AutoplayChecker } from '@/internal/utils/AutoplayChecker'
import { Quality } from '@/public/models/ILoadOptions'
import * as languages from '@/languages.json'
import '@/plugins/VokaKeyboardPlugin'
import '@/plugins/VokaHeartbeatPlugin'
import '@/plugins/VokaMetricsPlugin'
import '@/plugins/VokaMagicRemotePlugin'
import '@/components/Skin'
import { VokaContentType } from '@/public/models/VokaContentType'
import VokaWebOSTech from '@/internal/player/native/webos/tech/VokaWebOSTech'
import VokaTizenTech from '@/internal/player/native/tizen/tech/VokaTizenTech'
import VokaAppleTech from '@/internal/player/native/apple/tech/VokaAppleTech'
import VokaMp4Tech from '@/internal/player/native/mp4/tech/VokaMp4Tech'
import VokaHlsTech from '@/internal/player/native/hls/tech/VokaHlsTech'
import VokaDashTech from '@/internal/player/native/dash/tech/VokaDashTech'
import VokaEmptyTech from '@/internal/player/native/empty/VokaEmptyTech'
type VideoJsPlayerOptions = Parameters<typeof videojs>[1]
type PlayerReadyHandler = (player: VokaPlayerCore) => void
const playbackRates = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]
const Dom = videojs.dom
export type QualityData = {
index: number
bitrate: number
quality: Quality
label?: string
}
export class VokaPlayerCore {
static readonly DivID = 'vokaPlayerVideoTag'
private player!: Player
private autoPlaySupported!: boolean
private readonly stateEmitter: EventBus
// Constructor
constructor(element: HTMLElement, callback?: PlayerReadyHandler) {
const readyCallback = typeof callback === 'undefined' ? null : callback
this.stateEmitter = new EventBus()
/*this.stateEmitter.subscribe(
playerStateChange,
(data) => (this.state_ = data.payload.value)
)*/
AutoplayChecker.isAutoplaySupported((supported) => {
this.setupPlayer(element, supported, readyCallback)
})
}
private setupPlayer(
element: HTMLElement,
autoPlaySupported: boolean,
callback: PlayerReadyHandler | null
) {
this.autoPlaySupported = autoPlaySupported
// Create main container for player.
const playerContainer = Dom.createEl(
'video',
{ id: `${VokaPlayerCore.DivID}_${element.id}_` },
{}
)
element.appendChild(playerContainer)
videojs.log.level('debug')
const options = this.initOptions()
const player = videojs(playerContainer, options, () => {
if (callback != null) {
callback(this)
}
this.player.play()
// try load content just right after complete initialization.
/*this.stateEmitter.publish(
playerStateChange({ value: Initialized })
)*/
})
player.addClass('video-js')
/*if (userAgent.getDevice().type === DEVICE.MOBILE) {
player.addClass('vjs-mobile')
}*/
// Sign to events.
this.setupAttachListeners(player)
this.player = player
}
private initOptions(): VideoJsPlayerOptions {
// Required player's plugins.
const plugins: Record<string, any> = { }
//plugins.vokaQualityPlugin = {}
//plugins.vokaStatisticsPlugin = {}
//plugins.vokaAdvertisementPlugin = {}
//plugins.vokaCaptionsPlugin = {}
// plugins.vokaKeyboardPlugin = {
// skip: {
// forward: 5,
// backward: 5,
// },
// }
plugins.vokaMagicRemotePlugin = {
skip: {
forward: 5,
backward: 5,
},
}
plugins.vokaHeartbeatPlugin = {} // Хартбит Player.Heartbeat
plugins.vokaMetricsPlugin = {} // Метрики Player.Metrics
const childrenComponents = [
// Non-visual component, not in UI layer list.
'mediaLoader' // Required loader, that load tech list!
]
const skinChildren = [
'Skin',
'resizeManager',
'LoadingSpinner',
'PosterImage',
'RestrictionBox',
'liveTracker',
]
childrenComponents.push(...skinChildren)
// Settings for player.
return {
techOrder: [
VokaWebOSTech.TECH_NAME,
VokaTizenTech.TECH_NAME,
VokaAppleTech.TECH_NAME,
VokaMp4Tech.TECH_NAME,
VokaHlsTech.TECH_NAME,
VokaDashTech.TECH_NAME,
VokaEmptyTech.TECH_NAME
], // Order for teches important.
plugins, // Attached plugins for player.
languages,
language: 'ru',
children: childrenComponents,
poster: undefined,
responsive: true,
breakpoints: {
tiny: undefined,
xsmall: undefined,
small: 559,
medium: 1002,
large: undefined,
xlarge: undefined,
huge: Infinity
},
playbackRates,
sources: [
// Dash
/*{
src: "https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd",
content: VokaContentType.DASH,
type: "application/dash+xml"
}*/
// { src: "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8", type: "application/x-mpegURL" }
// { src: "https://dai.google.com/linear/hls/event/rtcMlf4RTvOEkaudeany5w/master.m3u8?iu=/4128/CBS.NY.OTT", type: "application/x-mpegURL" }
/*{
src: "https://vjs.zencdn.net/v/oceans.mp4",
type: "video/mp4",
parameters: {
stream: {
url: "https://vjs.zencdn.net/v/oceans.mp4"
}
}
}*/
// Apple Fairplay DRM
/*{
src: "https://codeeducation.akamaized.net/code/fullcycle/devops_20/01/01_introducao.mp4/fp/fairplay.m3u8",
content: VokaContentType.FAIRPLAY,
protection: {
keySystem: "com.apple.fps.1_0",
certificateUrl: "https://codeeducation.akamaized.net/fairplay.cer",
licenseUrl: "https://fps.ezdrm.com/api/licenses/F6B15258-BC92-49EB-9CDF-DE9F121C13A5?customdata=MTQ0OmFyZ2VudGluYWx1aXpAZ21haWwuY29tOjY3MTE6Y291cnNlOmNvZGU="
}
}*/
// Dash WideWine DRM
/*{
src: "https://media.axprod.net/TestVectors/Cmaf/protected_1080p_h264_cbcs/manifest.mpd",
content: VokaContentType.WIDEVINE,
keySystemOptions: [{
name: "com.widevine.alpha",
options: {
"serverURL": "https://drm-widevine-licensing.axtest.net/AcquireLicense",
"httpRequestHeaders": {
"X-AxDRM-Message": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJ2ZXJzaW9uIjogMSwKICAiY29tX2tleV9pZCI6ICI2OWU1NDA4OC1lOWUwLTQ1MzAtOGMxYS0xZWI2ZGNkMGQxNGUiLAogICJtZXNzYWdlIjogewogICAgInR5cGUiOiAiZW50aXRsZW1lbnRfbWVzc2FnZSIsCiAgICAidmVyc2lvbiI6IDIsCiAgICAibGljZW5zZSI6IHsKICAgICAgImFsbG93X3BlcnNpc3RlbmNlIjogdHJ1ZQogICAgfSwKICAgICJjb250ZW50X2tleXNfc291cmNlIjogewogICAgICAiaW5saW5lIjogWwogICAgICAgIHsKICAgICAgICAgICJpZCI6ICIzMDJmODBkZC00MTFlLTQ4ODYtYmNhNS1iYjFmODAxOGEwMjQiLAogICAgICAgICAgImVuY3J5cHRlZF9rZXkiOiAicm9LQWcwdDdKaTFpNDNmd3YremZ0UT09IiwKICAgICAgICAgICJ1c2FnZV9wb2xpY3kiOiAiUG9saWN5IEEiCiAgICAgICAgfQogICAgICBdCiAgICB9LAogICAgImNvbnRlbnRfa2V5X3VzYWdlX3BvbGljaWVzIjogWwogICAgICB7CiAgICAgICAgIm5hbWUiOiAiUG9saWN5IEEiLAogICAgICAgICJwbGF5cmVhZHkiOiB7CiAgICAgICAgICAibWluX2RldmljZV9zZWN1cml0eV9sZXZlbCI6IDE1MCwKICAgICAgICAgICJwbGF5X2VuYWJsZXJzIjogWwogICAgICAgICAgICAiNzg2NjI3RDgtQzJBNi00NEJFLThGODgtMDhBRTI1NUIwMUE3IgogICAgICAgICAgXQogICAgICAgIH0KICAgICAgfQogICAgXQogIH0KfQ._NfhLVY7S6k8TJDWPeMPhUawhympnrk6WAZHOVjER6M"
},
priority: 0
}
}]
}*/
// Dash Playready DRM
/*{
src: "https://media.axprod.net/TestVectors/Cmaf/protected_1080p_h264_cbcs/manifest.mpd",
content: VokaContentType.PLAYREADY,
keySystemOptions: [{
name: "com.microsoft.playready",
options: {
"serverURL": "https://drm-widevine-licensing.axtest.net/AcquireLicense",
"httpRequestHeaders": {
"X-AxDRM-Message": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJ2ZXJzaW9uIjogMSwKICAiY29tX2tleV9pZCI6ICI2OWU1NDA4OC1lOWUwLTQ1MzAtOGMxYS0xZWI2ZGNkMGQxNGUiLAogICJtZXNzYWdlIjogewogICAgInR5cGUiOiAiZW50aXRsZW1lbnRfbWVzc2FnZSIsCiAgICAidmVyc2lvbiI6IDIsCiAgICAibGljZW5zZSI6IHsKICAgICAgImFsbG93X3BlcnNpc3RlbmNlIjogdHJ1ZQogICAgfSwKICAgICJjb250ZW50X2tleXNfc291cmNlIjogewogICAgICAiaW5saW5lIjogWwogICAgICAgIHsKICAgICAgICAgICJpZCI6ICIzMDJmODBkZC00MTFlLTQ4ODYtYmNhNS1iYjFmODAxOGEwMjQiLAogICAgICAgICAgImVuY3J5cHRlZF9rZXkiOiAicm9LQWcwdDdKaTFpNDNmd3YremZ0UT09IiwKICAgICAgICAgICJ1c2FnZV9wb2xpY3kiOiAiUG9saWN5IEEiCiAgICAgICAgfQogICAgICBdCiAgICB9LAogICAgImNvbnRlbnRfa2V5X3VzYWdlX3BvbGljaWVzIjogWwogICAgICB7CiAgICAgICAgIm5hbWUiOiAiUG9saWN5IEEiLAogICAgICAgICJwbGF5cmVhZHkiOiB7CiAgICAgICAgICAibWluX2RldmljZV9zZWN1cml0eV9sZXZlbCI6IDE1MCwKICAgICAgICAgICJwbGF5X2VuYWJsZXJzIjogWwogICAgICAgICAgICAiNzg2NjI3RDgtQzJBNi00NEJFLThGODgtMDhBRTI1NUIwMUE3IgogICAgICAgICAgXQogICAgICAgIH0KICAgICAgfQogICAgXQogIH0KfQ._NfhLVY7S6k8TJDWPeMPhUawhympnrk6WAZHOVjER6M"
},
priority: 0
}
}]
}*/
],
tracks: [
{
kind: "subtitles",
src: "https://raw.githubusercontent.com/videojs/video.js/c7298d40a4632a6e9dfcd5a2f5cc3bbe92a78744/docs/examples/elephantsdream/captions.ru.vtt",
srclang: 'ru',
label: 'russian',
},
{
kind: "subtitles",
src: "https://raw.githubusercontent.com/videojs/video.js/c7298d40a4632a6e9dfcd5a2f5cc3bbe92a78744/docs/examples/shared/example-captions.vtt",
srclang: 'en',
label: 'english',
}
],
qualities: [
{
res: 480,
label: "480p",
},
{
res: 720,
label: "720p",
},
{
res: 1080,
label: "1080p",
}
],
audio: [
{
res: "ru",
label: "Русский",
},
{
res: "en",
label: "Анлийский",
},
],
skip: {
forward: 5,
backward: 5,
},
controls: {
play: true,
replay: true,
volume: {
inline: true,
},
progress: true,
zoom: true,
fullscreen: true,
},
selection: true,
previewPopup: {
imageCallback: (percent: number) => {
return ''
// timeline from voka (for test)
// return `https://streaming.voka.tv/vod_preview/velcom/4W17sRxFu8eCAps3kdTxJrFk7d46DNmum_320x180.jpeg?preview_pos=${percent}`;
},
},
enableDocumentPictureInPicture: true,
}
}
private setupAttachListeners(player: Player) {
// Добавляем листенеры здесь
player.on('loadstart', () => {
// TODO: удалить, после подключения субтитров не из внешенго источника
document.querySelector('.vjs-tech')?.setAttribute('crossOrigin', 'anonymous')
})
}
}
@@ -5,7 +5,7 @@ import {
VokaOptionsType
} from '../../VokaSourceHandler'
import { VokaContentType } from '@/public/models/VokaContentType'
import { isNativePlayback } from '@/internal/utils/PlatformCapabilities'
import PlatformCapabilities from '@/internal/utils/PlatformCapabilities'
import VokaAppleTech from '../tech/VokaAppleTech'
const Tech = videojs.getTech('Tech')
@@ -38,7 +38,7 @@ export default class VokaAppleSourceHandler extends VokaSourceHandler {
return ''
}
if (isNativePlayback()) {
if (PlatformCapabilities.isNativePlayback()) {
return 'probably'
}
@@ -1,7 +1,7 @@
import videojs from "video.js"
import VokaEvent from '@/constants/VokaEvent'
import VokaDashSourceHandler from '../sourcehandler/VokaDashSourceHandler'
import { isSupportedMSE } from '@/internal/utils/PlatformCapabilities'
import PlatformCapabilities from '@/internal/utils/PlatformCapabilities'
import { DashContext } from '../processors/DashContext'
import VokaTech from '@/internal/player/native/VokaTech'
import { DashErrorType } from '@/internal/drm/dash/VokaDash'
@@ -161,7 +161,7 @@ class VokaDashTech extends VokaTech {
}
return !!(
isSupportedMSE() &&
PlatformCapabilities.isSupportedMSE() &&
VokaDashSourceHandler.VIDEO_TEST_TAG() &&
VokaDashSourceHandler.VIDEO_TEST_TAG().canPlayType
)
@@ -7,7 +7,7 @@ import * as Fn from '@/internal/utils/fn'
import { wait } from '@/monads/Monoids'
import VokaHlsSourceHandler from '@/internal/player/native/hls/sourcehandler/VokaHlsSourceHandler'
import HlsLoadContext from '@/internal/player/native/hls/processors/HlsLoadContext'
import { isSupportedHlsJs } from '@/internal/utils/PlatformCapabilities'
import PlatformCapabilities, { isSupportedHlsJs } from '@/internal/utils/PlatformCapabilities'
import VokaEvent from '@/constants/VokaEvent'
import { IVokaSource, VokaSourceHandler } from '../../VokaSourceHandler'
import { bus } from '@/internal/events/bus'
@@ -399,7 +399,7 @@ class VokaHlsTech extends VokaTech {
}
return !!(
isSupportedHlsJs &&
PlatformCapabilities.isSupportedHlsJs &&
VokaHlsSourceHandler.VIDEO_TEST_TAG() &&
VokaHlsSourceHandler.VIDEO_TEST_TAG().canPlayType
)
+4 -2
View File
@@ -194,7 +194,7 @@ namespace BrowserUtils {
const userAgent = (navigator.userAgent || '').toLowerCase()
const vendor = (navigator.vendor || '').toLowerCase()
[
const knownBrowsers =[
{ name: 'Edge', prefix: ' edge/' },
{ name: 'Edge2', prefix: ' edg/' },
{ name: 'Opera', prefix: ' opr/' },
@@ -206,7 +206,9 @@ namespace BrowserUtils {
{ name: 'Chrome', prefix: ' chrome/', vendor: 'google' },
{ name: 'Firefox', prefix: ' firefox/' },
{ name: 'IE', prefix: ' trident/' }
].forEach((element) => {
]
knownBrowsers.forEach((element) => {
if (result.name != null) { return }
if (element.vendor != undefined && vendor.indexOf(element.vendor) < 0) { return }
+13
View File
@@ -17,6 +17,19 @@ namespace EncryptSystem {
return 'com.apple.fps.1_0'
}
}
export function toString(system: EncryptSystem | null): string {
switch (system) {
case EncryptSystem.playready:
return 'playready'
case EncryptSystem.widevine:
return 'widevine'
case EncryptSystem.fairplay:
return 'fairplay'
default:
return 'none'
}
}
}
class EMESystem {
+37 -34
View File
@@ -2,43 +2,46 @@ import Hls from 'hls.js'
import videojs from 'video.js'
const Browser = videojs.browser
/**
* Помимо проверки на MSE, выполняет также проверку
*
* mediaSource.isTypeSupported('video/mp4 codecs="avc1.42E01E,mp4a.40.2"')
*/
const isSupportedHlsJs: boolean = Hls.isSupported()
namespace PlatformCapabilities {
function getMediaSource(): typeof MediaSource | undefined {
return (window as any).MediaSource || (window as any).WebKitMediaSource
}
/**
* Помимо проверки на MSE, выполняет также проверку
*
* mediaSource.isTypeSupported('video/mp4 codecs="avc1.42E01E,mp4a.40.2"')
*/
export const isSupportedHlsJs: boolean = Hls.isSupported()
/**
* поддержка MediaSourceExtension - необходимое и, возможно, достаточное (maybe, т.к. до проверки type) условие для работы и hls.js, и DASH.
* Код проверки из hls.js isSupported()
* но без проверки на isTypeSupported - эту проверку лучше вырполнять не на захардкоденном кодеке, а в canPlayType теча.
*/
const isSupportedMSE = function (): boolean {
const mediaSource = getMediaSource()
if (!mediaSource) {
return false
export function getMediaSource(): typeof MediaSource | undefined {
return (window as any).MediaSource || (window as any).WebKitMediaSource
}
const sourceBuffer: any =
self.SourceBuffer || ((self as any).WebKitSourceBuffer as SourceBuffer)
// if SourceBuffer is exposed ensure its API is valid
// safari and old version of Chrome doe not expose SourceBuffer globally so checking SourceBuffer.prototype is impossible
const sourceBufferValidAPI =
!sourceBuffer ||
(sourceBuffer.prototype &&
typeof sourceBuffer.prototype.appendBuffer === 'function' &&
typeof sourceBuffer.prototype.remove === 'function')
return !!sourceBufferValidAPI
/**
* поддержка MediaSourceExtension - необходимое и, возможно, достаточное (maybe, т.к. до проверки type) условие для работы и hls.js, и DASH.
* Код проверки из hls.js isSupported()
* но без проверки на isTypeSupported - эту проверку лучше вырполнять не на захардкоденном кодеке, а в canPlayType теча.
*/
export const isSupportedMSE = function (): boolean {
const mediaSource = getMediaSource()
if (!mediaSource) {
return false
}
const sourceBuffer: any =
self.SourceBuffer || ((self as any).WebKitSourceBuffer as SourceBuffer)
// if SourceBuffer is exposed ensure its API is valid
// safari and old version of Chrome doe not expose SourceBuffer globally so checking SourceBuffer.prototype is impossible
const sourceBufferValidAPI =
!sourceBuffer ||
(sourceBuffer.prototype &&
typeof sourceBuffer.prototype.appendBuffer === 'function' &&
typeof sourceBuffer.prototype.remove === 'function')
return !!sourceBufferValidAPI
}
export const isNativePlayback = function (): boolean {
//return !HlsProcessor.isSupported;
return Browser.IS_IOS && !isSupportedHlsJs
}
}
const isNativePlayback = function (): boolean {
//return !HlsProcessor.isSupported;
return Browser.IS_IOS && !isSupportedHlsJs
}
export { isSupportedMSE, isSupportedHlsJs, isNativePlayback }
export default PlatformCapabilities
+2 -2
View File
@@ -27,8 +27,8 @@ namespace SupportedCodecs {
}
export enum NativePlayerType {
hls,
mss
hls = "hls",
mss = "mss"
}
export class SupportedCodecs {
+167 -67
View File
@@ -1,3 +1,5 @@
import { VokaOptions } from '@/public/@types'
export enum VokaPlayerEvent {
'DurationChange' = 'durationchange',
'TimeUpdate' = 'timeupdate',
@@ -57,73 +59,171 @@ toolboxEndSel - toolbox end selection button was clicked
toolboxProcessSel - toolbox process selection button was clicked
* */
export interface IVokaPlayer {
/// addEventListener - add listener of specific event
on(event: VokaPlayerEvent, callback: Function): void
/// removeEventListener - remove listener of event
off(event: VokaPlayerEvent, callback?: Function): void
/// start/resume playing
play(): Promise<void>
/// pause playing
pause(): Promise<void>
stop(): Promise<void>
export interface ITimeRange {
start: number
end: number
}
/*
load(url: string, options?: ILoadOptions): Promise<void>
loadWithProvider(
provider: IAPIProvider,
options?: ILoadOptions
): Promise<void>
setCurrentTime(value: number): Promise<void>
setVolume(value: number): Promise<void>
setPlaybackRate(value: PlaybackRate): Promise<void>
setViewType(value: string): Promise<void>
setQuality(value: Quality): Promise<void>*/
export interface IQuality {
bitrate: number | null
width: number | null
height: number | null
}
/*
* afterInitialize - register callback function that will be called after player is initialized (it will be called immediately if player is already initialized)
* isInitialized - check if player is initialized
* getProtocol - get supported streaming protocol name
* getDrmSystem - get supported drm system name
* isHlsSupported - check if HLS protocol is supported
* getVideoCodecs - get list of supported video codecs for dash protocol (only among those that were enabled in config)
* attachSource - set stream url and stream options
* getPaused - get paused state
* getIsLive - get live streaming flag
* seek - seek stream to specific position, in seconds
* getCurrentTime - get current stream position, in seconds
* getDuration - get current stream duration, in seconds
* getAbsoluteCurrentTime - get current utc unixtime for live streams
* getAbsoluteTimeRange - get utc start/end range for live streams
* getTimeshiftAvailable - check if timeshift is available
* getVideoQualityList - get array with information about available video qualities
* getSelectedVideoQuality - get index of currently selected video quality (-1 for auto)
* getPlayingVideoQuality - get index of currently playing video quality (-1 if unknown)
* setSelectedVideoQuality - set index of video quality to play (-1 for auto)
* getAudioTrackList - get array of available audio tracks
* getCurrentAudioTrack - get index of currently selected audio track
* setCurrentAudioTrack - set index of audio track to play
* getSubtitlesTrackList - get array of available subtitles tracks
* getCurrentSubtitlesTrack - get index of currently selected subtitles track (-1 for disabled)
* setCurrentSubtitlesTrack - set index of subtitles track to display (-1 to disable)
* setSelectionStartPos - mark current position as selection start
* setSelectionEndPos - mark current position as selection end
* getSelectionRange - get currently selected range
* getZoomButtonVisible - get zoom button visibility
* getZoomModeEnabled - check if zoom mode is enabled
* setZoomModeEnabled - enable/disable zoom mode (boolean argument)
* setVolume - set audio volume
* getVolume - get audio volume
* mute - mute audio
* unmute - unmute audio
* getMuted - get muted state
* getBufferLength - get length in seconds of buffered data
* getNetworkBandwidth - get current network bandwidth in kbit/s
* getBufferingState - wheither or not playback is stalled due to buffering
* getCurrentVideoInfo - get information about currently playing video track
* getControlbarVisible - get visibility state of control bar
* getAdIsPlaying - player is currently loading/playing ads
* cancelAdPlayback - cancel currently playing advertisement(s)
* destroy - destroy "player" object
*/
export interface IAudioTrack {
index: number
lang: string
label: string
}
export interface ISubtitle {
index: number
lang: string
label: string
}
export interface IVokaPlayer {
/// register callback function that will be called after player is initialized (it will be called immediately if player is already initialized)
afterInitialize(callback: () => void): void
/// check if player is initialized
isInitialized(): boolean
/// get supported streaming protocol name
getProtocol(): string
/// get supported drm system name
getDrmSystem(): string
/// check if HLS protocol is supported
isHlsSupported(): boolean
/// get list of supported video codecs for dash protocol (only among those that were enabled in config)
getVideoCodecs(): [string]
/// set stream url and stream options
attachSource(url: string, options: VokaOptions.IStreamOptions): void
/// get paused state
getPaused(): boolean
/// get live streaming flag
getIsLive(): boolean
/// seek stream to specific position, in seconds
seek(seconds: number): void
/// get current stream position, in seconds
getCurrentTime(): number
/// get current stream duration, in seconds
getDuration(): number
/// set audio volume
setVolume(value: number)
/// get audio volume
getVolume(): number
/// mute audio
mute(): void
/// unmute audio
unmute(): void
/// get muted state
getMuted(): boolean
/// destroy
destroy()
/// get length in seconds of buffered data
getBufferLength(): number
/// wheither or not playback is stalled due to buffering
getBufferingState(): boolean
/// player is currently loading/playing ads
getAdIsPlaying(): boolean
/// cancel currently playing advertisement(s)
cancelAdPlayback()
/// get current utc unixtime for live streams
getAbsoluteCurrentTime(): number | null
/// get utc start/end range for live streams
getAbsoluteTimeRange(): ITimeRange | null
/// check if timeshift is available
getTimeshiftAvailable(): boolean
/// get array with information about available video qualities
getVideoQualityList(): [IQuality]
/// get index of currently selected video quality (-1 for auto)
getSelectedVideoQuality(): number
/// get index of currently playing video quality (-1 if unknown)
getPlayingVideoQuality(): number
/// set index of video quality to play (-1 for auto)
setSelectedVideoQuality(index: number)
/// get array of available audio tracks
getAudioTrackList(): [IAudioTrack]
/// get index of currently selected audio track
getCurrentAudioTrack(): number
/// set index of audio track to play
setCurrentAudioTrack(index: number)
/// get array of available subtitles tracks
getSubtitlesTrackList(): [ISubtitle]
/// get index of currently selected subtitles track (-1 for disabled)
getCurrentSubtitlesTrack(): number
/// set index of subtitles track to display (-1 to disable)
setCurrentSubtitlesTrack(index: number)
/// mark current position as selection start
setSelectionStartPos(seconds: number)
/// mark current position as selection end
setSelectionEndPos(seconds: number)
/// get currently selected range
getSelectionRange(): ITimeRange | null
/// get zoom button visibility
getZoomButtonVisible(): boolean
/// check if zoom mode is enabled
getZoomModeEnabled(): boolean
/// enable/disable zoom mode (boolean argument)
setZoomModeEnabled(isEnabled: boolean)
/// get current network bandwidth in kbit/s
getNetworkBandwidth(): number
/// get information about currently playing video track
getCurrentVideoInfo(): any
/// get visibility state of control bar
getControlbarVisible(): boolean
/// addEventListener - add listener of specific event
on(event: VokaPlayerEvent, callback: Function): void
/// removeEventListener - remove listener of event
off(event: VokaPlayerEvent, callback?: Function): void
/// start/resume playing
play(): Promise<void>
/// pause playing
pause(): void
}
+18
View File
@@ -0,0 +1,18 @@
namespace VokaGlobalFunctions {
export const value = ""
/*
GlobalFuncs.log = playerLog;
GlobalFuncs.enableExternalLog = enableExternalLog;
GlobalFuncs.getPlayerVersion = getPlayerVersion;
GlobalFuncs.getProtocolAndDrm = getProtocolAndDrm;
GlobalFuncs.copyOptions = copyOptions;
GlobalFuncs.normalizeOptions = normalizeOptions;
GlobalFuncs.formatTime = formatTime;
* */
}
export default VokaGlobalFunctions
+21 -29
View File
@@ -1,12 +1,13 @@
import { VokaErrorMessages, VokaOptions } from './@types'
import { VokaOptions } from './@types'
import { IVokaPlayer } from './IVokaPlayer'
import { EMESystem, EncryptSystem } from '@/internal/utils/EMESystem'
import { VokaPlayerCore } from '@/internal/player/VokaPlayerCore'
import VokaCorePlayer from '@/internal/player/VokaCorePlayer'
import GUIDUtils from '@/internal/utils/GUIDUtils'
import { defaultOptions } from './models/VokaDefaultOptions'
import { VokaPlayerImpl } from '@/public/VokaPlayerImpl'
import SupportedCodecs from '@/internal/utils/SupportedCodecs'
import BrowserUtils from '@/internal/utils/BrowserUtils'
import VokaGlobalFunctions from '@/public/VokaGlobalFunctions'
namespace VokaPlayer {
@@ -23,10 +24,10 @@ namespace VokaPlayer {
export function features(): SupportedCodecs.ISelectProtocolResult | null { return this.playerFeatures }
export async function create(
element: HTMLElement | string,
export function create(
element: HTMLElement | string | null,
creationOptions: VokaOptions.IOptions | null
): Promise<IVokaPlayer> {
): IVokaPlayer | VokaGlobalFunctions {
let htmlElement: HTMLElement | null = null
if (typeof document !== 'undefined') {
@@ -38,9 +39,7 @@ namespace VokaPlayer {
}
if (htmlElement == null || !BrowserUtils.isDomElement(htmlElement)) {
return Promise.reject(
new TypeError(VokaErrorMessages.DocumentUnavailable)
)
return VokaGlobalFunctions
}
// TODO: Добавить возможность получить предыдущий инстанс плеера передав этот же элемент
@@ -52,29 +51,21 @@ namespace VokaPlayer {
}*/
if ((htmlElement as HTMLElement).nodeName == 'IFRAME') {
return Promise.reject(
new TypeError(VokaErrorMessages.IFrameElement)
)
return VokaGlobalFunctions
}
const options = { ...defaultOptions, ...creationOptions }
const result = await detectFeatures(options)
this.playerFeatures = Object.freeze(result)
return new Promise<IVokaPlayer>((resolve) => {
new VokaPlayerCore(htmlElement, (player) => {
resolve(
new VokaPlayerImpl(
GUIDUtils.globalUnique, // id
GUIDUtils.generateGUID(), // SessionGUID
options, // merge options default and passed from outside
result,
player
)
)
return new VokaPlayerImpl(
GUIDUtils.globalUnique, // id
GUIDUtils.generateGUID(), // SessionGUID
options, // merge options default and passed from outside
detectFeatures(options),
new Promise<VokaCorePlayer.CorePlayer>((resolve) => {
new VokaCorePlayer.CorePlayer(htmlElement, (player) => {
resolve(player)
})
})
})
)
}
export async function detectFeatures(options: VokaOptions.IOptions): Promise<SupportedCodecs.ISelectProtocolResult> {
@@ -97,8 +88,9 @@ namespace VokaPlayer {
try {
system = await EMESystem.detect()
} catch { }
return SupportedCodecs.SupportedCodecs.selectProtocol(system, BrowserUtils.isMobile(), options.features.drm, protocolOptions)
const result = SupportedCodecs.SupportedCodecs.selectProtocol(system, BrowserUtils.isMobile(), options.features.drm, protocolOptions)
this.playerFeatures = Object.freeze(result)
return this.playerFeatures
}
}
+250 -20
View File
@@ -1,67 +1,220 @@
import { VokaOptions } from './@types'
import { IVokaPlayer, VokaPlayerEvent } from './IVokaPlayer'
import { VokaPlayerCore } from '@/internal/player/VokaPlayerCore'
import { IAudioTrack, IQuality, ISubtitle, ITimeRange, IVokaPlayer, VokaPlayerEvent } from './IVokaPlayer'
import VokaCorePlayer from '@/internal/player/VokaCorePlayer'
import '../assets/scss/main.scss'
import VokaApi from '@/public/network/VokaApi'
import BrowserUtils from '@/internal/utils/BrowserUtils'
import SupportedCodecs from '@/internal/utils/SupportedCodecs'
import { EncryptSystem } from '@/internal/utils/EMESystem'
import { VokaContentType } from '@/public/models/VokaContentType'
import { Promise } from 'es6-promise'
import PlatformCapabilities from '@/internal/utils/PlatformCapabilities'
import IResult = VokaApi.IResult
import CorePlayer = VokaCorePlayer.CorePlayer
export class VokaPlayerImpl implements IVokaPlayer {
private static readonly version = '0.0.3'
private static readonly build = '1'
private readonly player: VokaPlayerCore
private _player: VokaCorePlayer.CorePlayer | null
public readonly uniqID: number
public readonly sessionGUID: string
public readonly options: VokaOptions.IOptions
private readonly initializePromise: Promise<void>
private _features: SupportedCodecs.ISelectProtocolResult | null
public static playerVersion(): string { return `${this.version}.${this.build}` }
private constructor(
id: number,
sessionGUID: string,
options: VokaOptions.IOptions,
features: SupportedCodecs.ISelectProtocolResult,
player: VokaPlayerCore
features: Promise<SupportedCodecs.ISelectProtocolResult>,
playerPromise: Promise<VokaCorePlayer.CorePlayer>
) {
this.uniqID = id
this.sessionGUID = sessionGUID
this.options = options
this.player = player
this._player = null
this.initialize(features)
this.initializePromise = new Promise((resolve) => {
Promise.all([features, playerPromise]).then(
([features, player]) => {
this._player = player
this._features = features
this.initialize(player, features).then((result) => { resolve() })
}
)
})
}
// MARK: - IVokaPlayer implementation
public afterInitialize(callback: () => void) {
this.initializePromise.then(() => { callback() })
}
public isInitialized(): boolean {
return this._player != null
}
public getProtocol(): string {
const features = this._features
if (features != null && features.native != null) {
return features.native.toString()
}
return 'dash'
}
public getDrmSystem(): string {
const features = this._features
if (!this.options.features.drm || features == null) {
return 'none'
}
return EncryptSystem.toString(features.keySystem)
}
public isHlsSupported(): boolean {
return PlatformCapabilities.isSupportedHlsJs
}
public getVideoCodecs(): [string] {
const features = this._features
if (features == null) { return [] }
return features.mseCodecs
}
public attachSource(url: string, options: VokaOptions.IStreamOptions): void {
// TODO!!!!
}
public getPaused(): boolean {
if (this._player == null) { return false }
return this._player.isPaused
}
public getIsLive(): boolean {
if (this._player == null) { return false }
return this._player.isLive
}
public seek(seconds: number): void {
if (this._player == null) { return }
this._player.seek(seconds)
}
public getCurrentTime(): number {
if (this._player == null) { return 0 }
return this._player.currentTime
}
public getDuration(): number {
if (this._player == null) { return 0 }
return this._player.duration
}
public setVolume(value: number) {
if (this._player == null) { return }
this._player.setVolume(value)
}
public getVolume(): number {
if (this._player == null) { return 0 }
return this._player.volume
}
public mute() {
if (this._player == null) { return }
this._player.mute(true)
}
public unmute() {
if (this._player == null) { return }
this._player.mute(false)
}
public getMuted(): boolean {
if (this._player == null) { return false }
return this._player.isMuted
}
public destroy() {
// TODO!!!
}
getBufferLength(): number {
if (this._player == null) { return 0 }
return this._player.bufferLength
}
getBufferingState(): boolean {
if (this._player == null) { return false }
return this._player.isBuffering
}
getAdIsPlaying(): boolean { return false }
cancelAdPlayback() {
// TODO
}
getAbsoluteCurrentTime(): number | null { return null }
getAbsoluteTimeRange(): ITimeRange | null { return null }
getTimeshiftAvailable(): boolean { return false }
getVideoQualityList(): [IQuality] { return [] }
getSelectedVideoQuality(): number { return 0 }
getPlayingVideoQuality(): number { return 0 }
setSelectedVideoQuality(index: number) { }
getAudioTrackList(): [IAudioTrack] { return [] }
getCurrentAudioTrack(): number { return 0 }
setCurrentAudioTrack(index: number) {}
getSubtitlesTrackList(): [ISubtitle] { return [] }
getCurrentSubtitlesTrack(): number { return 0 }
setCurrentSubtitlesTrack(index: number) {}
setSelectionStartPos(seconds: number) {}
setSelectionEndPos(seconds: number) {}
getSelectionRange(): ITimeRange | null { return null }
getZoomButtonVisible(): boolean { return false }
getZoomModeEnabled(): boolean { return false }
setZoomModeEnabled(isEnabled: boolean) {}
getNetworkBandwidth(): number { return 0 }
getCurrentVideoInfo(): any { return {} }
getControlbarVisible(): boolean { return true }
on(event: VokaPlayerEvent, callback: Function): void {
throw new Error('Method not implemented.')
}
off(event: VokaPlayerEvent, callback?: Function): void {
throw new Error('Method not implemented.')
}
play(): Promise<void> {
throw new Error('Method not implemented.')
play(): Promise<void> | undefined {
if (this._player == null) { return Promise.resolve() }
return this._player.play()
}
pause(): Promise<void> {
throw new Error('Method not implemented.')
}
stop(): Promise<void> {
throw new Error('Method not implemented.')
pause(): void {
if (this._player == null) { return }
return this._player.pause()
}
// MARK: - Private methods
private async initialize(features: SupportedCodecs.ISelectProtocolResult) {
private load(content: IResult, player: CorePlayer) {
if (this.options.features.api) {
const content = this.fetchContent(this.options, features)
}
const iContent = {
url: content.url,
type: VokaContentType.HLS,
subtitlesUrl: content.subtitlesUrl,
} as VokaCorePlayer.IContent
player.load(iContent)
}
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)
this.load(content, player)
return true
}
private async fetchContent(options: VokaOptions.IOptions, features: SupportedCodecs.ISelectProtocolResult) {
@@ -97,6 +250,83 @@ export class VokaPlayerImpl implements IVokaPlayer {
} as VokaApi.ISystemCapability
const api = new VokaApi.Api(apiOptions, apiCapability)
const result = api.load()
return api.load()
}
}
}
// MP4
/*const iContent = {
url: "https://vjs.zencdn.net/v/oceans.mp4",
type: VokaContentType.MP4,
} as VokaCorePlayer.IContent*/
// HLS
/*const iContent = {
url: "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8",
type: VokaContentType.HLS,
} as VokaCorePlayer.IContent*/
// HLS LIVE
/*const iContent = {
url: "https://dai.google.com/linear/hls/event/rtcMlf4RTvOEkaudeany5w/master.m3u8?iu=/4128/CBS.NY.OTT",
type: VokaContentType.HLS,
} as VokaCorePlayer.IContent*/
// DASH
/*const iContent = {
url: "https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd",
type: VokaContentType.DASH,
} as VokaCorePlayer.IContent*/
// DASH
/*const iContent = {
url: "https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd",
type: VokaContentType.DASH,
} as VokaCorePlayer.IContent*/
// FAIRPLAY TODO
/*const iContent = {
url: "https://codeeducation.akamaized.net/code/fullcycle/devops_20/01/01_introducao.mp4/fp/fairplay.m3u8",
type: VokaContentType.FAIRPLAY,
drmConfig: {
type: VokaCorePlayer.DRMType.FAIRPLAY,
certificateUrl: "https://codeeducation.akamaized.net/fairplay.cer",
licenseUrl: "https://fps.ezdrm.com/api/licenses/F6B15258-BC92-49EB-9CDF-DE9F121C13A5?customdata=MTQ0OmFyZ2VudGluYWx1aXpAZ21haWwuY29tOjY3MTE6Y291cnNlOmNvZGU=",
}
} as VokaCorePlayer.IContent*/
/*const iContent = {
url: "https://e09f957480c8b1e479a1edb0fabc72d8.egress.mediapackage-vod.eu-west-1.amazonaws.com/out/v1/6f12444e79macdf3e4206ad363f810cb2aead/9ea4e8148b794c8ba2c6295b824e5ad5/46a61bf2c081464bb9476f2a55a06f48/index.m3u8",
type: VokaContentType.FAIRPLAY,
drmConfig: {
type: VokaCorePlayer.DRMType.FAIRPLAY,
certificateUrl: "https://customer-tests.la.drm.cloud/certificate/fairplay?BrandGuid=5a96a0d0-d13f-42b0-ab2b-ba8cfc4aa0a0",
licenseUrl: "https://customer-tests.la.drm.cloud/acquire-license/fairplay?KID=4376a4b3-d8ef-4f21-9a6b-faa81a2e59e3&brandguid=5a96a0d0-d13f-42b0-ab2b-ba8cfc4aa0a0&usertoken=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MzU2ODk2MDAsImRybVRva2VuSW5mbyI6eyJleHAiOiIyMDI1LTEyLTA3VDE1OjMwOjA5LjU5MDgxMjUrMDE6MDAiLCJraWQiOlsiKiJdLCJwIjp7InBlcnMiOnRydWUsImVkIjoiMjAyNS0xMi0wN1QxNTozMDowOS41OTExMzA1KzAxOjAwIn19fQ.xEToUttAk9AVFgP3bHyDlcvm6BR-8_hsl8V3n-jrDwM",
}
} as VokaCorePlayer.IContent*/
// Dash WideWine DRM
/*const iContent = {
url: "https://media.axprod.net/TestVectors/Cmaf/protected_1080p_h264_cbcs/manifest.mpd",
type: VokaContentType.WIDEVINE,
drmConfig: {
type: VokaCorePlayer.DRMType.WIDEWINE,
certificateUrl: "https://drm-widevine-licensing.axtest.net/AcquireLicense",
headers: {
"X-AxDRM-Message": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJ2ZXJzaW9uIjogMSwKICAiY29tX2tleV9pZCI6ICI2OWU1NDA4OC1lOWUwLTQ1MzAtOGMxYS0xZWI2ZGNkMGQxNGUiLAogICJtZXNzYWdlIjogewogICAgInR5cGUiOiAiZW50aXRsZW1lbnRfbWVzc2FnZSIsCiAgICAidmVyc2lvbiI6IDIsCiAgICAibGljZW5zZSI6IHsKICAgICAgImFsbG93X3BlcnNpc3RlbmNlIjogdHJ1ZQogICAgfSwKICAgICJjb250ZW50X2tleXNfc291cmNlIjogewogICAgICAiaW5saW5lIjogWwogICAgICAgIHsKICAgICAgICAgICJpZCI6ICIzMDJmODBkZC00MTFlLTQ4ODYtYmNhNS1iYjFmODAxOGEwMjQiLAogICAgICAgICAgImVuY3J5cHRlZF9rZXkiOiAicm9LQWcwdDdKaTFpNDNmd3YremZ0UT09IiwKICAgICAgICAgICJ1c2FnZV9wb2xpY3kiOiAiUG9saWN5IEEiCiAgICAgICAgfQogICAgICBdCiAgICB9LAogICAgImNvbnRlbnRfa2V5X3VzYWdlX3BvbGljaWVzIjogWwogICAgICB7CiAgICAgICAgIm5hbWUiOiAiUG9saWN5IEEiLAogICAgICAgICJwbGF5cmVhZHkiOiB7CiAgICAgICAgICAibWluX2RldmljZV9zZWN1cml0eV9sZXZlbCI6IDE1MCwKICAgICAgICAgICJwbGF5X2VuYWJsZXJzIjogWwogICAgICAgICAgICAiNzg2NjI3RDgtQzJBNi00NEJFLThGODgtMDhBRTI1NUIwMUE3IgogICAgICAgICAgXQogICAgICAgIH0KICAgICAgfQogICAgXQogIH0KfQ._NfhLVY7S6k8TJDWPeMPhUawhympnrk6WAZHOVjER6M",
},
}
} as VokaCorePlayer.IContent*/
// Dash Playready DRM
/*const iContent = {
url: "https://media.axprod.net/TestVectors/Cmaf/protected_1080p_h264_cbcs/manifest.mpd",
type: VokaContentType.PLAYREADY,
drmConfig: {
type: VokaCorePlayer.DRMType.PLAYREADY,
certificateUrl: "https://drm-widevine-licensing.axtest.net/AcquireLicense",
headers: {
"X-AxDRM-Message": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJ2ZXJzaW9uIjogMSwKICAiY29tX2tleV9pZCI6ICI2OWU1NDA4OC1lOWUwLTQ1MzAtOGMxYS0xZWI2ZGNkMGQxNGUiLAogICJtZXNzYWdlIjogewogICAgInR5cGUiOiAiZW50aXRsZW1lbnRfbWVzc2FnZSIsCiAgICAidmVyc2lvbiI6IDIsCiAgICAibGljZW5zZSI6IHsKICAgICAgImFsbG93X3BlcnNpc3RlbmNlIjogdHJ1ZQogICAgfSwKICAgICJjb250ZW50X2tleXNfc291cmNlIjogewogICAgICAiaW5saW5lIjogWwogICAgICAgIHsKICAgICAgICAgICJpZCI6ICIzMDJmODBkZC00MTFlLTQ4ODYtYmNhNS1iYjFmODAxOGEwMjQiLAogICAgICAgICAgImVuY3J5cHRlZF9rZXkiOiAicm9LQWcwdDdKaTFpNDNmd3YremZ0UT09IiwKICAgICAgICAgICJ1c2FnZV9wb2xpY3kiOiAiUG9saWN5IEEiCiAgICAgICAgfQogICAgICBdCiAgICB9LAogICAgImNvbnRlbnRfa2V5X3VzYWdlX3BvbGljaWVzIjogWwogICAgICB7CiAgICAgICAgIm5hbWUiOiAiUG9saWN5IEEiLAogICAgICAgICJwbGF5cmVhZHkiOiB7CiAgICAgICAgICAibWluX2RldmljZV9zZWN1cml0eV9sZXZlbCI6IDE1MCwKICAgICAgICAgICJwbGF5X2VuYWJsZXJzIjogWwogICAgICAgICAgICAiNzg2NjI3RDgtQzJBNi00NEJFLThGODgtMDhBRTI1NUIwMUE3IgogICAgICAgICAgXQogICAgICAgIH0KICAgICAgfQogICAgXQogIH0KfQ._NfhLVY7S6k8TJDWPeMPhUawhympnrk6WAZHOVjER6M",
},
}
} as VokaCorePlayer.IContent*/
+4 -2
View File
@@ -1,5 +1,6 @@
import { createEventDefinition, EventBus } from 'ts-bus'
import { HTTPClient, HTTPMethod, IHTTPClient, RequestOptions } from '../../internal/utils/HTTPClient'
import { VokaOptions } from '@/public/@types'
namespace VokaApi {
@@ -67,7 +68,8 @@ namespace VokaApi {
url: string
subtitlesUrl: string | null
drmConfig: IDRMConfig | null
metrics: IMetrics | null
metrics: IMetrics | null,
streamOptions: VokaOptions.IStreamOptions | null
}
export class Api {
@@ -135,7 +137,7 @@ namespace VokaApi {
}
const result = await client.request(
HTTPMethod.POST,
HTTPMethod.GET,// HTTPMethod.POST,
"//" + this.getApiHost() + "/v1/devices.json",
queryParams,
null,