#66575 WebOS : Каналы стартуют медленнее чем в старом плеере, на фильмах,...

This commit is contained in:
Алексей Манаев
2025-08-28 23:22:22 +00:00
parent 9314cf4512
commit 75e5c73a46
6 changed files with 186 additions and 85 deletions
+2 -2
View File
@@ -25,7 +25,7 @@
var player = spbtvplayer('my-video', {
log: true,
features: {
api: true,
api: false,
drm: false,
metrics: true
},
@@ -63,7 +63,7 @@
player.afterInitialize(() => {
console.log("afterInitialize")
// player.setControlbarVisibility(true)
// player.attachSource("https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd")
player.attachSource("https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd", {autoplay: true})
})
player.addEventListener('play', onPlay, window)
+1
View File
@@ -8,6 +8,7 @@ services:
- USER_UID=1000
- USER_GID=1000
restart: always
command: "serve.sh"
volumes:
- $PWD:/usr/voka
- node_modules:/usr/voka/node_modules
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
set -e
# Собираем production-бандл
echo "[VOKA] Building production bundle..."
#npm run build-prod
nodemon --watch src --watch vendors -e ts,js,scss --exec npm run build &
# Проверим, что установлен serve, если нет — установим
if ! npx --no-install serve -v >/dev/null 2>&1; then
echo "[VOKA] Installing 'serve'..."
npm install -g serve
fi
# Запускаем локальный сервер на 5000 порту (или любой другой)
echo "[VOKA] Starting server at http://localhost:8080"
serve -s demo -l 8080
+22 -23
View File
@@ -407,24 +407,26 @@ namespace VokaCorePlayer {
// не подходит для хромкаст. Временное решение для сохранения исходника.
this.player._originalSrc = playableContent
this.bus.publish(VokaBusEvent.switchContent(content))
this.bus.publish(
VokaBusEvent.adsMarkersSet({
items: [
{
index: 0,
timestamp: 10
},
{
index: 1,
timestamp: 60
},
{
index: 2,
timestamp: 90
}
]
})
)
// MARK: Пример задания рекламных точек
// this.bus.publish(
// VokaBusEvent.adsMarkersSet({
// items: [
// {
// index: 0,
// timestamp: 10
// },
// {
// index: 1,
// timestamp: 60
// },
// {
// index: 2,
// timestamp: 90
// }
// ]
// })
// )
return Promise.resolve()
}
@@ -497,9 +499,6 @@ namespace VokaCorePlayer {
private initOptions(options: CorePlayerOptions.IOptions): VideoJsPlayerOptions {
// Required player's plugins.
const plugins: Record<string, any> = { }
//plugins.vokaStatisticsPlugin = {}
//plugins.vokaAdvertisementPlugin = {}
//plugins.vokaCaptionsPlugin = {}
// plugins.vokaKeyboardPlugin = {
// skip: {
// forward: 5,
@@ -579,7 +578,7 @@ namespace VokaCorePlayer {
language: 'ru',
children: childrenComponents,
// MARK: Пример постера
poster: 'https://i.ytimg.com/vi/u3q7GGLpiqk/maxresdefault.jpg',
// poster: 'https://i.ytimg.com/vi/u3q7GGLpiqk/maxresdefault.jpg',
responsive: true,
breakpoints: {
tiny: undefined,
@@ -625,7 +624,7 @@ namespace VokaCorePlayer {
previewPopup: {
imageCallback: (percent: number) => {
return ''
// timeline from voka (for test)
// MARK: пример ссылки на превью
// return `https://streaming.voka.tv/vod_preview/velcom/4W17sRxFu8eCAps3kdTxJrFk7d46DNmum_320x180.jpeg?preview_pos=${percent}`;
},
},
+12 -2
View File
@@ -1,3 +1,4 @@
import videojs from "video.js";
import { VokaOptions } from './@types'
import { IVokaPlayer } from './IVokaPlayer'
import EncryptSystem from '@/internal/utils/EncryptSystem'
@@ -10,6 +11,11 @@ import BrowserUtils from '@/internal/utils/BrowserUtils'
import VokaGlobalFunctions from '@/public/VokaGlobalFunctions'
import ObjectUtils from '@/internal/utils/ObjectUtils'
function log(...args) {
const now = new Date().toISOString(); // формат ISO: 2025-08-25T10:15:30.123Z
videojs.log(`[${now}][VokaPlayer]`, ...args);
}
namespace VokaPlayer {
let playerFeatures: SupportedCodecs.ISelectProtocolResult | null = null
@@ -29,8 +35,9 @@ namespace VokaPlayer {
element: HTMLElement | string | null,
creationOptions: VokaOptions.IOptions | null
): IVokaPlayer | VokaGlobalFunctions {
log("Player constructor started")
let htmlElement: HTMLElement | null = null
let htmlElement: HTMLElement | null = null
if (typeof document !== 'undefined') {
if (typeof element === 'string') {
htmlElement = document.getElementById(element) as HTMLElement
@@ -57,13 +64,16 @@ namespace VokaPlayer {
const id = GUIDUtils.globalUnique
const options = ObjectUtils.mergeDeep({}, defaultOptions, creationOptions, { loggerId: id } as VokaOptions.IOptions)
return new VokaPlayerImpl(
const player = new VokaPlayerImpl(
id, // id
GUIDUtils.generateGUID(), // SessionGUID
options, // merge options default and passed from outside
detectFeatures(options),
VokaCorePlayer.createPlayer(htmlElement),
)
log("Player constructor finished")
return player
}
export async function detectFeatures(options: VokaOptions.IOptions): Promise<SupportedCodecs.ISelectProtocolResult> {
+132 -58
View File
@@ -1,3 +1,4 @@
import { BusEvent } from "ts-bus/types";
import videojs from 'video.js'
import { VokaOptions } from './@types'
import { IAudioTrack, IQuality, ISubtitle, ITimeRange, IVideoInfo, IVokaPlayer } from './IVokaPlayer'
@@ -16,6 +17,11 @@ import { EventBus } from 'ts-bus'
import { VokaError } from '@/public/models/VokaError'
import VokaBusEvent, { adError, adFinished, adStarted } from '@/internal/events/VokaBusEvent'
function log(...args) {
const now = new Date().toISOString()
videojs.log(`[${ now }][VokaPlayerImpl]`, ...args)
}
export class VokaPlayerImpl implements IVokaPlayer {
private static readonly version = '0.0.3'
@@ -35,11 +41,11 @@ export class VokaPlayerImpl implements IVokaPlayer {
public static playerVersion(): string { return `${this.version}.${this.build}` }
constructor(
id: number,
sessionGUID: string,
options: VokaOptions.IOptions,
features: Promise<SupportedCodecs.ISelectProtocolResult>,
playerCreationClosure: VokaCorePlayer.PlayerCreationClosure
id: number,
sessionGUID: string,
options: VokaOptions.IOptions,
features: Promise<SupportedCodecs.ISelectProtocolResult>,
playerCreationClosure: VokaCorePlayer.PlayerCreationClosure,
) {
if (!options.log) videojs.log.level("off")
@@ -53,47 +59,115 @@ export class VokaPlayerImpl implements IVokaPlayer {
const playerOptions = this.prepareCoreOptions(options)
this.initializePromise = new Promise(
(resolve, reject) => {
Promise.all([features, playerCreationClosure(playerOptions)]).then(
([features, player]) => {
this._player = player
this._features = features
this.initialize(player, features).then((result) => { resolve() }, reject)
if (!!options.log) {
videojs.log("[OPTIONS PREPARE]:" + this.uniqID, playerOptions);
(resolve, reject) => {
Promise.all([features, playerCreationClosure(playerOptions)]).then(
([features, player]) => {
this._player = player
this._features = features
this.initialize(player, features).then((result) => {
resolve()
}, reject)
// Применяем декоратор ко всем public методам класса
const allMethods = Object.getOwnPropertyNames(this.__proto__)
const toRemove = ["constructor", "prepareCoreOptions", "load", "initialize", "safeStringify", "logMethodCall"]
for (const propertyName of allMethods.filter(method => !toRemove.includes(method))) {
const descriptor = Object.getOwnPropertyDescriptor(this.__proto__, propertyName);
if (descriptor && typeof descriptor.value === 'function') {
Object.defineProperty(this.__proto__, propertyName, this.logMethodCall(this.__proto__, propertyName, descriptor));
const INSTANCE_DECORATED = Symbol("instanceDecorated")
const DECORATED_METHODS = Symbol("decoratedMethods")
if (options.log && !(this as any)[INSTANCE_DECORATED]) {
log("[OPTIONS PREPARE]:" + this.uniqID, playerOptions)
const proto = Object.getPrototypeOf(this)
const toSkip = new Set([
"constructor",
"prepareCoreOptions",
"load",
"initialize",
"safeStringify",
"logMethodCall",
])
const decorated: Set<string> = new Set()
Object.defineProperty(this, DECORATED_METHODS, {
value: decorated,
configurable: true,
})
for (const name of Object.getOwnPropertyNames(proto)) {
if (toSkip.has(name)) continue
const d = Object.getOwnPropertyDescriptor(proto, name)
if (!d || typeof d.value !== "function") continue
if (Object.prototype.hasOwnProperty.call(this, name)) continue
const instanceDesc: PropertyDescriptor = {
configurable: true,
enumerable: d.enumerable ?? false,
writable: true,
value: d.value,
}
const wrappedDesc = this.logMethodCall(proto, name, instanceDesc)
wrappedDesc.value.__logged = true
decorated.add(name)
Object.defineProperty(this, name, wrappedDesc)
}
for (const name of Object.getOwnPropertyNames(this)) {
if (toSkip.has(name)) continue
if (decorated.has(name)) continue
const d = Object.getOwnPropertyDescriptor(this, name)
if (!d || typeof d.value !== "function") continue
if (d.value.__logged) continue
const instanceDesc: PropertyDescriptor = {
configurable: true,
enumerable: d.enumerable ?? true,
writable: true,
value: d.value,
}
const wrappedDesc = this.logMethodCall(this, name, instanceDesc)
wrappedDesc.value.__logged = true
decorated.add(name)
Object.defineProperty(this, name, wrappedDesc)
}
Object.defineProperty(this, INSTANCE_DECORATED, {
value: true,
configurable: true,
})
}
}
}
}
)
}
},
)
},
)
this.initializePromise.catch(e => {
console.error(e)
this.destroy()
});
})
this.bus.subscribe(
VokaBusEvent.adStarted,
event => { this._adIsPlaying = true },
VokaBusEvent.adStarted,
event => {
this._adIsPlaying = true
},
)
this.bus.subscribe(
VokaBusEvent.adFinished,
event => { this._adIsPlaying = false },
VokaBusEvent.adFinished,
event => {
this._adIsPlaying = false
},
)
this.bus.subscribe(
VokaBusEvent.adError,
event => { this._adIsPlaying = false },
VokaBusEvent.adError,
event => {
this._adIsPlaying = false
},
)
}
@@ -102,7 +176,7 @@ export class VokaPlayerImpl implements IVokaPlayer {
private prepareCoreOptions(options: VokaOptions.IOptions): CorePlayerOptions.IOptions {
if (!!options.log) {
videojs.log("[OPTIONS]:" + this.uniqID, options);
log("[OPTIONS]:" + this.uniqID, options)
}
return {
controls: {
@@ -247,40 +321,40 @@ export class VokaPlayerImpl implements IVokaPlayer {
}
private static safeStringify(obj: any, indent = 2): string {
const cache = new Set();
const cache = new Set()
return JSON.stringify(obj, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (cache.has(value)) {
return '[Circular]';
if (typeof value === "object" && value !== null) {
if (cache.has(value)) {
return "[Circular]"
}
cache.add(value)
}
cache.add(value);
}
// Убираем ненужные большие объекты (например, window или DOM-элементы)
if (value instanceof Window) return '[Window]';
if (value instanceof Document) return '[Document]';
if (value instanceof HTMLElement) return `[HTMLElement: ${value.tagName}]`;
return value;
}, indent);
}
// Убираем ненужные большие объекты (например, window или DOM-элементы)
if (value instanceof Window) return "[Window]"
if (value instanceof Document) return "[Document]"
if (value instanceof HTMLElement) return `[HTMLElement: ${ value.tagName }]`
return value
}, indent)
}
private logMethodCall(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
const originalMethod = descriptor.value
const id = this.uniqID
descriptor.value = function(...args: any[]) {
const res = originalMethod.apply(this, args);
descriptor.value = function (...args: any[]) {
const res = originalMethod.apply(this, args)
if (this.uniqID === id) {
const logKey ="[VokaPlayerImpl]:[ID=" + this.uniqID + "]:[" + propertyKey + "]:"
videojs.log(logKey,
" Arguments:" + VokaPlayerImpl.safeStringify(args),
" Result:" + VokaPlayerImpl.safeStringify(res)
);
console.trace(logKey)
const logKey = "[ID=" + this.uniqID + "]:[" + propertyKey + "]:"
log(logKey,
" Arguments:" + VokaPlayerImpl.safeStringify(args),
" Result:" + VokaPlayerImpl.safeStringify(res),
)
console.trace(logKey, "stacktrace: ")
}
return res
};
}
return descriptor;
return descriptor
}
// MARK: - IVokaPlayer implementation
@@ -413,7 +487,7 @@ export class VokaPlayerImpl implements IVokaPlayer {
getAdIsPlaying(): boolean { return this._adIsPlaying }
cancelAdPlayback() {
this.bus.publish(VokaBusEvent.adCancelPlaybackEvent)
this.bus.publish(VokaBusEvent.adCancelPlaybackEvent as BusEvent)
}
getAbsoluteCurrentTime(): number | null {