@vkontakte/videoplayer-core 2.0.179-dev.4cee6bcff.0 → 2.0.179-dev.62f578567.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/es2015.cjs +17 -17
  2. package/es2015.esm.js +16 -16
  3. package/esnext.cjs +16 -16
  4. package/esnext.esm.js +15 -15
  5. package/evergreen.esm.js +16 -16
  6. package/package.json +2 -2
  7. package/types/player/Player.d.ts +1 -1
  8. package/types/player/types.d.ts +2 -0
  9. package/types/providers/DashProvider/lib/buffer.d.ts +2 -0
  10. package/types/providers/DashProvider/lib/fetcher.d.ts +9 -4
  11. package/types/providers/DashProvider/lib/player.d.ts +1 -0
  12. package/types/providers/DashProviderVirtual/lib/buffer/types.d.ts +2 -0
  13. package/types/providers/DashProviderVirtual/lib/buffer/virtualBuffer/baseVirtualBufferManager.d.ts +3 -1
  14. package/types/providers/DashProviderVirtual/lib/buffer/virtualBuffer/byteRangeVirtualBufferManager.d.ts +1 -0
  15. package/types/providers/DashProviderVirtual/lib/fetcher.d.ts +9 -4
  16. package/types/providers/DashProviderVirtual/lib/player/basePlayer.d.ts +5 -2
  17. package/types/providers/ProviderContainer/index.d.ts +3 -0
  18. package/types/providers/types.d.ts +2 -2
  19. package/types/providers/utils/Abr/abrController.d.ts +7 -4
  20. package/types/providers/utils/Abr/controllers/historyController.d.ts +67 -0
  21. package/types/providers/utils/Abr/controllers/oscillationController.d.ts +83 -0
  22. package/types/providers/utils/Abr/controllers/trackOutcomeController.d.ts +53 -0
  23. package/types/providers/utils/Abr/rules/video/banBasedOnHistoryRule.d.ts +17 -0
  24. package/types/providers/utils/Abr/types.d.ts +6 -3
  25. package/types/providers/utils/Abr/utils.d.ts +1 -0
  26. package/types/providers/utils/segmentCache/IndexedDbSegmentCacheStorage.d.ts +54 -0
  27. package/types/providers/utils/segmentCache/MediaSegmentBatchCache.d.ts +59 -0
  28. package/types/providers/utils/segmentCache/constants.d.ts +20 -0
  29. package/types/providers/utils/segmentCache/index.d.ts +8 -0
  30. package/types/providers/utils/segmentCache/segmentCacheDb.d.ts +25 -0
  31. package/types/providers/utils/segmentCache/segmentCacheUtils.d.ts +25 -0
  32. package/types/providers/utils/segmentCache/types.d.ts +85 -0
  33. package/types/utils/tuningConfig.d.ts +8 -9
  34. package/types/providers/DashProvider/lib/urlsCache.d.ts +0 -23
  35. package/types/providers/DashProviderVirtual/lib/urlsCache.d.ts +0 -22
  36. package/types/providers/utils/Abr/trackHistory.d.ts +0 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vkontakte/videoplayer-core",
3
- "version": "2.0.179-dev.4cee6bcff.0",
3
+ "version": "2.0.179-dev.62f578567.0",
4
4
  "author": "vk.ru",
5
5
  "description": "Videoplayer core library based on the vk.ru platform",
6
6
  "homepage": "https://vk.ru",
@@ -42,6 +42,6 @@
42
42
  "**/*.d.ts"
43
43
  ],
44
44
  "dependencies": {
45
- "@vkontakte/videoplayer-shared": "1.0.108-dev.4cee6bcff.0"
45
+ "@vkontakte/videoplayer-shared": "1.0.108-dev.62f578567.0"
46
46
  }
47
47
  }
@@ -30,7 +30,7 @@ export default class Player implements IPlayer {
30
30
  experimental: PlayerExperimentalImpl;
31
31
  private devNullLog;
32
32
  private collectDecodingInfoDestroyCb;
33
- private hasReleasedTimers;
33
+ private releaseTimers;
34
34
  constructor(tuning?: IOptionalTuningConfig, tracer?: ITracer, playerOptions?: IPlayerOptions);
35
35
  private readonly doInit;
36
36
  /**
@@ -315,12 +315,14 @@ export interface PlayerInfoValues {
315
315
  *
316
316
  * default value: `undefined`
317
317
  */
318
+ currentVirtualVideoBuffer$: StartEnd<Seconds> | undefined;
318
319
  currentVideoBuffer$: StartEnd<Seconds> | undefined;
319
320
  /**
320
321
  * Текущий аудио-буффер плеера (тот; который содержит текущую позицию воспроизведения)
321
322
  *
322
323
  * default value: `undefined`
323
324
  */
325
+ currentVirtualAudioBuffer$: StartEnd<Seconds> | undefined;
324
326
  currentAudioBuffer$: StartEnd<Seconds> | undefined;
325
327
  /**
326
328
  * Текущий браузерный видео-буффер (тот; который содержит текущую позицию воспроизведения)
@@ -42,6 +42,7 @@ export declare class BufferManager {
42
42
  private parsedInitData;
43
43
  private representations;
44
44
  private segments;
45
+ private segmentIndexMap;
45
46
  private allInitsLoaded;
46
47
  private activeSegments;
47
48
  private forwardBufferRepresentations;
@@ -110,6 +111,7 @@ export declare class BufferManager {
110
111
  private feedPreDownloadedFirstSegment;
111
112
  private loadByteRangeSegments;
112
113
  private prepareByteRangeFetchSegmentParams;
114
+ private buildCacheMetadata;
113
115
  private prepareTemplateFetchSegmentParams;
114
116
  private abortActiveSegments;
115
117
  private onSomeTemplateDataLoaded;
@@ -4,6 +4,7 @@ import type { Byte, IValueSubject, ISubject, Milliseconds, IRange, ITracer, IErr
4
4
  import { abortable } from "@vkontakte/videoplayer-shared";
5
5
  import type { ExactVideoQuality } from "@vkontakte/videoplayer-shared";
6
6
  import type { CommonInit, GenericContainerParser, Segment, SegmentReference } from "../../utils/parsers/types";
7
+ import type { ISegmentCacheStorage, CacheMetadata, SegmentCacheMode } from "../../utils/segmentCache";
7
8
  declare global {
8
9
  interface Navigator {
9
10
  connection: {
@@ -24,12 +25,13 @@ export interface IParams {
24
25
  tracer: ITracer;
25
26
  useEnableSubtitlesParam?: boolean;
26
27
  handleExtendedNetworkErrorsSet?: boolean;
27
- useUrlCacheMechanism?: boolean;
28
28
  measureNonSegmentRequests?: boolean;
29
29
  measureFastSamples?: boolean;
30
30
  cacheHitTtfbMaxMs?: Milliseconds;
31
31
  preloadFirstSegment?: boolean;
32
32
  maxQualityForFirstSegmentPreload?: ExactVideoQuality;
33
+ segmentCacheService?: ISegmentCacheStorage;
34
+ segmentCacheMode?: SegmentCacheMode;
33
35
  }
34
36
  export type Priority = "high" | "low" | "auto";
35
37
  export interface FetchParamsWithUrl extends FetchParams {
@@ -46,8 +48,8 @@ export interface FetchParams {
46
48
  isLowLatency?: boolean;
47
49
  bufferOptimisation?: boolean;
48
50
  ignoreNetworkErrors?: boolean;
49
- urlCacheMechanismEnabled?: boolean;
50
51
  representationHeight?: number;
52
+ cacheMetadata?: CacheMetadata;
51
53
  }
52
54
  export type RepresentationFetchResult = {
53
55
  init: CommonInit | null;
@@ -74,17 +76,18 @@ export declare class Fetcher {
74
76
  private readonly subscription;
75
77
  private compatibilityMode;
76
78
  private useEnableSubtitlesParam;
77
- private useUrlCacheMechanism;
78
79
  private measureNonSegmentRequests;
79
80
  private measureFastSamples;
80
81
  private cacheHitTtfbMaxMs;
81
82
  private preloadFirstSegment;
82
83
  private maxQualityForFirstSegmentPreload;
84
+ private segmentCacheService;
85
+ private mediaBatchCache;
83
86
  private startupPhase;
84
87
  private performanceObserver;
85
88
  private pendingConnectionMetrics;
86
89
  private handleExtendedNetworkErrorsSet;
87
- constructor({ throughputEstimator, requestQuic, tracer, compatibilityMode, useEnableSubtitlesParam, handleExtendedNetworkErrorsSet, useUrlCacheMechanism, measureNonSegmentRequests, measureFastSamples, cacheHitTtfbMaxMs, preloadFirstSegment, maxQualityForFirstSegmentPreload }: IParams);
90
+ constructor({ throughputEstimator, requestQuic, tracer, compatibilityMode, useEnableSubtitlesParam, handleExtendedNetworkErrorsSet, measureNonSegmentRequests, measureFastSamples, cacheHitTtfbMaxMs, preloadFirstSegment, maxQualityForFirstSegmentPreload, segmentCacheService, segmentCacheMode }: IParams);
88
91
  private onHeadersReceived;
89
92
  private setupPerformanceObserver;
90
93
  private processPerformanceResourceTiming;
@@ -95,6 +98,8 @@ export declare class Fetcher {
95
98
  fetchManifest: ReturnType<typeof abortable<[string], string | null>>;
96
99
  fetch: ReturnType<typeof abortable<[string, FetchParams], ArrayBuffer | null>>;
97
100
  fetchRepresentation(segmentReference: SegmentReference, parser: GenericContainerParser<unknown>, fetchParams: FetchParams): Promise<RepresentationFetchResult | null>;
101
+ /** Скипнутые сегменты не записаны в кэш — при следующем запросе региона докачаются как miss. */
102
+ private traceSkippedCacheSegments;
98
103
  destroy(): void;
99
104
  private fetchByteRangeRepresentation;
100
105
  private fetchTemplateRepresentation;
@@ -40,6 +40,7 @@ export declare class Player {
40
40
  private subscriptionRemovable;
41
41
  private representationSubscription;
42
42
  private fetcher;
43
+ private segmentCacheService;
43
44
  state$: StateMachine<State>;
44
45
  currentVideoRepresentation$: IValueSubject<Representation["id"] | undefined>;
45
46
  currentVideoRepresentationInit$: IValueSubject<CommonInit | undefined>;
@@ -3,6 +3,7 @@ import type { CommonInit, Manifest, Representation, Segment } from "../../../uti
3
3
  import type { Fetcher } from "../fetcher";
4
4
  import type { ITuningConfig } from "../../../../utils/tuningConfig";
5
5
  import type { VideoSegmentLoadProgress } from "../../../utils/Abr/types";
6
+ import type { ISegmentCacheStorage } from "../../../utils/segmentCache";
6
7
  export type TRepresentationSwitchMode = "lazy" | "force";
7
8
  export declare enum SwithRepresentationMode {
8
9
  Lazy = "lazy",
@@ -15,6 +16,7 @@ export interface Dependencies {
15
16
  tuning: ITuningConfig;
16
17
  compatibilityMode?: boolean;
17
18
  manifest: Manifest | null;
19
+ segmentCacheService?: ISegmentCacheStorage;
18
20
  getCurrentPosition(): Milliseconds | undefined;
19
21
  getCurrentStallDuration(): Milliseconds;
20
22
  isActiveLowLatency(): boolean;
@@ -2,6 +2,7 @@ import type { Fetcher, FetchParamsWithUrl, Priority, RepresentationFetchResult }
2
2
  import type { CommonInit, ContainerParser, Representation, Segment } from "../../../../utils/parsers/types";
3
3
  import { StreamKind } from "../../../../utils/parsers/types";
4
4
  import type { ITuningConfig } from "../../../../../utils/tuningConfig";
5
+ import type { ISegmentCacheStorage } from "../../../../utils/segmentCache";
5
6
  import type { Byte, IError, IRange, ISubject, ITracer, IValueSubject, Milliseconds, Seconds } from "@vkontakte/videoplayer-shared";
6
7
  import { abortable, Subscription } from "@vkontakte/videoplayer-shared";
7
8
  import type { NativeBufferManager } from "../nativeBufferManager";
@@ -19,6 +20,7 @@ export declare abstract class BaseVirtualBufferManager<T extends Segment> implem
19
20
  protected readonly fetcher: Fetcher;
20
21
  protected readonly tracer: ITracer;
21
22
  protected readonly tuning: ITuningConfig;
23
+ protected readonly segmentCacheService: ISegmentCacheStorage | undefined;
22
24
  protected representations: Map<Representation["id"], Representation>;
23
25
  protected playingRepresentationId: Representation["id"] | undefined;
24
26
  protected downloadingRepresentationId: Representation["id"] | undefined;
@@ -54,7 +56,7 @@ export declare abstract class BaseVirtualBufferManager<T extends Segment> implem
54
56
  protected readonly getCurrentPosition: () => Milliseconds | undefined;
55
57
  protected readonly getCurrentStallDuration: () => Milliseconds | undefined;
56
58
  protected brokenSegmentAppendDetected: boolean;
57
- protected constructor(kind: StreamKind, nativeBufferManager: NativeBufferManager, representations: Representation[], { fetcher, tracer, tuning, getCurrentPosition, getCurrentStallDuration, manifest }: Dependencies);
59
+ protected constructor(kind: StreamKind, nativeBufferManager: NativeBufferManager, representations: Representation[], { fetcher, tracer, tuning, getCurrentPosition, getCurrentStallDuration, manifest, segmentCacheService }: Dependencies);
58
60
  protected abstract loadItems(itemsToLoad: IBufferPlaybackQueueItem<T>[], representation: Representation, priority?: Priority): Promise<void>;
59
61
  protected abstract selectItemsToLoad(): IBufferPlaybackQueueItem<T>[];
60
62
  protected abstract prepareFetchParams(items: IBufferPlaybackQueueItem<T>[], representation: Representation): FetchParamsWithUrl;
@@ -8,6 +8,7 @@ export declare class ByteRangeVirtualBufferManager extends BaseVirtualBufferMana
8
8
  protected override loadItems(itemsToLoad: IBufferPlaybackQueueItem<ByteRangeSegment>[], representation: Representation, priority?: Priority): Promise<void>;
9
9
  protected override selectItemsToLoad(): IBufferPlaybackQueueItem<ByteRangeSegment>[];
10
10
  protected override prepareFetchParams(items: IBufferPlaybackQueueItem<ByteRangeSegment>[], representation: Representation): FetchParamsWithUrl;
11
+ private buildCacheMetadata;
11
12
  /**
12
13
  * Закидываем в буфер сегменты атомарнее чем сегмент целиком. Например, по боксам в мпеге и по блокам в вебме.
13
14
  * Таким образом не ждём его полной загрузки и готовы играть его намного быстрее
@@ -4,6 +4,7 @@ import type { Byte, IError, IRange, ISubject, ITracer, IValueSubject, Millisecon
4
4
  import { abortable } from "@vkontakte/videoplayer-shared";
5
5
  import type { ExactVideoQuality } from "@vkontakte/videoplayer-shared";
6
6
  import type { CommonInit, GenericContainerParser, Segment, SegmentReference, StreamKind } from "../../utils/parsers/types";
7
+ import type { ISegmentCacheStorage, CacheMetadata, SegmentCacheMode } from "../../utils/segmentCache";
7
8
  declare global {
8
9
  interface Navigator {
9
10
  connection: {
@@ -24,12 +25,13 @@ export interface IParams {
24
25
  tracer: ITracer;
25
26
  useEnableSubtitlesParam?: boolean;
26
27
  handleExtendedNetworkErrorsSet?: boolean;
27
- useUrlCacheMechanism?: boolean;
28
28
  measureNonSegmentRequests?: boolean;
29
29
  measureFastSamples?: boolean;
30
30
  cacheHitTtfbMaxMs?: Milliseconds;
31
31
  preloadFirstSegment?: boolean;
32
32
  maxQualityForFirstSegmentPreload?: ExactVideoQuality;
33
+ segmentCacheService?: ISegmentCacheStorage;
34
+ segmentCacheMode?: SegmentCacheMode;
33
35
  }
34
36
  export type Priority = "high" | "low" | "auto";
35
37
  export interface FetchParamsWithUrl extends FetchParams {
@@ -45,8 +47,8 @@ export interface FetchParams {
45
47
  measureThroughput?: boolean;
46
48
  isLowLatency?: boolean;
47
49
  bufferOptimisation?: boolean;
48
- urlCacheMechanismEnabled?: boolean;
49
50
  kind: StreamKind;
51
+ cacheMetadata?: CacheMetadata;
50
52
  }
51
53
  export type RepresentationFetchResult<T extends Segment> = {
52
54
  initMetadata: CommonInit | null;
@@ -73,17 +75,18 @@ export declare class Fetcher {
73
75
  private subscription;
74
76
  private compatibilityMode;
75
77
  private useEnableSubtitlesParam;
76
- private useUrlCacheMechanism;
77
78
  private measureNonSegmentRequests;
78
79
  private measureFastSamples;
79
80
  private cacheHitTtfbMaxMs;
80
81
  private preloadFirstSegment;
81
82
  private maxQualityForFirstSegmentPreload;
83
+ private segmentCacheService;
84
+ private mediaBatchCache;
82
85
  private startupPhase;
83
86
  private performanceObserver;
84
87
  private pendingConnectionMetrics;
85
88
  private handleExtendedNetworkErrorsSet;
86
- constructor({ throughputEstimator, requestQuic, tracer, compatibilityMode, useEnableSubtitlesParam, handleExtendedNetworkErrorsSet, useUrlCacheMechanism, measureNonSegmentRequests, measureFastSamples, cacheHitTtfbMaxMs, preloadFirstSegment, maxQualityForFirstSegmentPreload }: IParams);
89
+ constructor({ throughputEstimator, requestQuic, tracer, compatibilityMode, useEnableSubtitlesParam, handleExtendedNetworkErrorsSet, measureNonSegmentRequests, measureFastSamples, cacheHitTtfbMaxMs, preloadFirstSegment, maxQualityForFirstSegmentPreload, segmentCacheService, segmentCacheMode }: IParams);
87
90
  private onHeadersReceived;
88
91
  private setupPerformanceObserver;
89
92
  private processResourceTiming;
@@ -94,6 +97,8 @@ export declare class Fetcher {
94
97
  fetchManifest: ReturnType<typeof abortable<[string], string | null>>;
95
98
  fetch: ReturnType<typeof abortable<[string, FetchParams], ArrayBuffer | null>>;
96
99
  fetchRepresentation<T extends Segment>(segmentReference: SegmentReference, parser: GenericContainerParser<unknown>, dowbloadAbortSignal: AbortSignal, priority?: Priority, representationHeight?: number, kind?: StreamKind): Promise<RepresentationFetchResult<T> | null | undefined>;
100
+ /** Скипнутые сегменты не записаны в кэш — при следующем запросе региона докачаются как miss. */
101
+ private traceSkippedCacheSegments;
97
102
  destroy(): void;
98
103
  private fetchByteRangeRepresentation;
99
104
  private fetchTemplateRepresentation;
@@ -9,6 +9,7 @@ import type { IError, IRange, ISubject, ISubscription, ITracer, IValueSubject, M
9
9
  import { abortable, Subject, Subscription, SubscriptionRemovable } from "@vkontakte/videoplayer-shared";
10
10
  import type { VideoSegmentLoadProgress } from "../../../utils/Abr/types";
11
11
  import { Fetcher } from "../fetcher";
12
+ import type { ISegmentCacheStorage } from "../../../utils/segmentCache";
12
13
  import type { Dependencies, IVirtualBufferManager, SwithRepresentationMode } from "../buffer/types";
13
14
  import type { Params } from "./types";
14
15
  import { State } from "./types";
@@ -34,6 +35,7 @@ export declare abstract class BasePlayer {
34
35
  protected subscriptionRemovable: SubscriptionRemovable;
35
36
  protected representationSubscription: Subscription;
36
37
  protected fetcher: Fetcher;
38
+ protected segmentCacheService: ISegmentCacheStorage | undefined;
37
39
  protected forceEnded$: ISubject<void>;
38
40
  protected stallWatchdogSubscription: ISubscription | undefined;
39
41
  protected destroyController: AbortController;
@@ -62,10 +64,11 @@ export declare abstract class BasePlayer {
62
64
  liveSeekableDuration$: IValueSubject<Milliseconds>;
63
65
  liveAvailabilityStartTime$: IValueSubject<Milliseconds>;
64
66
  liveStreamStatus$: IValueSubject<LiveStreamStatus | undefined>;
65
- currentVideoBuffer$: IValueSubject<IRange<Seconds>>;
66
- currentAudioBuffer$: IValueSubject<IRange<Seconds>>;
67
+ currentVirtualVideoBuffer$: IValueSubject<IRange<Seconds>>;
68
+ currentVirtualAudioBuffer$: IValueSubject<IRange<Seconds>>;
67
69
  bufferLength$: IValueSubject<Milliseconds>;
68
70
  nativeBufferLength$: IValueSubject<Milliseconds>;
71
+ nativeVideoBufferLength$: IValueSubject<IRange<Seconds>>;
69
72
  nativeAudioBufferLength$: IValueSubject<IRange<Seconds>>;
70
73
  liveLatency$: IValueSubject<Milliseconds | undefined>;
71
74
  liveLoadBufferLength$: IValueSubject<Milliseconds>;
@@ -37,6 +37,7 @@ export default class ProviderContainer implements IProviderContainer {
37
37
  private currentProviderStarted;
38
38
  private volumeMultiplierManager;
39
39
  private dashMaxTvVideoQuality;
40
+ private segmentCacheCleared;
40
41
  private mayday;
41
42
  private maydayStatistics;
42
43
  constructor(params: IParams);
@@ -50,6 +51,8 @@ export default class ProviderContainer implements IProviderContainer {
50
51
  private destroyProvider;
51
52
  private handleMaydayRecommendation;
52
53
  private applyErrorSideEffects;
54
+ /** Битые данные в кэше могут вызывать повторные ошибки после реинита — очищаем кэш целиком. */
55
+ private clearSegmentCacheOnError;
53
56
  private createProvider;
54
57
  private createScreenProvider;
55
58
  private createChromecastProvider;
@@ -81,8 +81,8 @@ export interface IProviderOutput {
81
81
  autoVideoTrackLimits$: IValueSubject<QualityLimits | undefined>;
82
82
  autoVideoTrackLimitingAvailable$: IValueSubject<boolean>;
83
83
  canChangePlaybackSpeed$: IValueSubject<boolean | undefined>;
84
- currentVideoBuffer$: IValueSubject<IRange<Seconds> | undefined>;
85
- currentAudioBuffer$: IValueSubject<IRange<Seconds> | undefined>;
84
+ currentVirtualVideoBuffer$: IValueSubject<IRange<Seconds> | undefined>;
85
+ currentVirtualAudioBuffer$: IValueSubject<IRange<Seconds> | undefined>;
86
86
  currentNativeVideoBuffer$: IValueSubject<IRange<Seconds> | undefined>;
87
87
  currentNativeAudioBuffer$: IValueSubject<IRange<Seconds> | undefined>;
88
88
  totalBufferDuration$: IValueSubject<Milliseconds | undefined>;
@@ -1,14 +1,16 @@
1
1
  import type { AbrLimitSources, IAbrController, IAbrRule, IBaseAbrControllerContext, IRuleInitContext, RuleLogData } from "./types";
2
2
  import type { IBaseTrack } from "../../../player/types";
3
+ import { HistoryController } from "./controllers/historyController";
3
4
  export declare const formatRuleLog: ({ name, ...logData }: RuleLogData) => string;
4
5
  export default class AbrController<
5
6
  T extends IBaseTrack,
6
7
  C extends IBaseAbrControllerContext
7
8
  > implements IAbrController<T> {
8
- private history;
9
+ private historyController;
9
10
  private rules;
10
11
  private _context;
11
12
  private _limitSources;
13
+ constructor(historyController?: HistoryController<T>);
12
14
  get context(): C;
13
15
  get limitSources(): AbrLimitSources;
14
16
  init(rules: IAbrRule<T, C>[], initContext: IRuleInitContext): void;
@@ -17,7 +19,8 @@ export default class AbrController<
17
19
  autoSelectTrack(rules?: IAbrRule<T, C>[]): T;
18
20
  private traceQualitySwitch;
19
21
  private isLowQuality;
20
- private selectTrackBasedOnHistory;
21
- /** при переключении аудио стрима очищаем историю для предотвращения залипания в `onCooldown` */
22
- historyReset(): void;
22
+ /** Полный сброс истории: cooldown, осцилляции и баны. */
23
+ resetHistory(): void;
24
+ resetOscillation(): void;
25
+ resetTrackOutcome(): void;
23
26
  }
@@ -0,0 +1,67 @@
1
+ import type { IBaseTrack } from "../../../../player/types";
2
+ import type { IObservable } from "@vkontakte/videoplayer-shared";
3
+ import type { ITuningConfig } from "../../../../utils/tuningConfig";
4
+ import type { IBaseAbrControllerContext } from "../types";
5
+ import type { TrackBans } from "./trackOutcomeController";
6
+ import type { OscillationReturnStats } from "./oscillationController";
7
+ import type { TrackOutcomeStats } from "./trackOutcomeController";
8
+ /** Телеметрия истории переключений: разрезы осцилляции и исходов (баны, испытательный срок). */
9
+ export type HistoryStats = {
10
+ oscillation: OscillationReturnStats;
11
+ outcome: TrackOutcomeStats | undefined;
12
+ };
13
+ type SelectBasedOnHistoryParams<T extends IBaseTrack> = {
14
+ candidate: T;
15
+ context: IBaseAbrControllerContext;
16
+ };
17
+ type ConstructorParams = {
18
+ /**
19
+ * Конфиг автоподбора качества; при отсутствии — только cooldown включается.
20
+ * oscillationCooldownEnabled включает влияние осцилляционного замка на выбор,
21
+ * trackOutcomeEnabled — применение банов; сбор журнала и исходов идёт всегда.
22
+ */
23
+ autoTrackSelection?: ITuningConfig["autoTrackSelection"];
24
+ severeStallOccurred$?: IObservable<boolean>;
25
+ isSeeked$?: IObservable<boolean>;
26
+ };
27
+ /**
28
+ * Решает, допускается ли переключение дорожки: трековый cooldown и доказанная осцилляция.
29
+ * Владеет историей выборов дорожек, детекцией осцилляции (OscillationController)
30
+ * и исходами переключений (TrackOutcomeController) — в исходы попадают только выполненные
31
+ * переключения. Состояние ведётся по id дорожек одного вида (видео или аудио) —
32
+ * общий инстанс для разных видов смешал бы осцилляции и баны.
33
+ */
34
+ export declare class HistoryController<T extends IBaseTrack = IBaseTrack> {
35
+ private lastApprovedTrack;
36
+ private lastSelectedAtById;
37
+ private readonly oscillationController;
38
+ private readonly outcomeController;
39
+ private readonly oscillationLockEnabled;
40
+ private readonly trackOutcomeEnabled;
41
+ private readonly subscription;
42
+ constructor({ autoTrackSelection, severeStallOccurred$, isSeeked$ }?: ConstructorParams);
43
+ get last(): T | undefined;
44
+ /**
45
+ * Полный сценарий гейтирования против истории переключений: решение по кандидату
46
+ * (cooldown / осцилляция), обновление всей истории (выборы, журнал, исходы)
47
+ * и телеметрия при блоке. Возвращает трек, который следует выбрать.
48
+ */
49
+ selectBasedOnHistory({ candidate, context }: SelectBasedOnHistoryParams<T>): T;
50
+ private decide;
51
+ private isUpgrade;
52
+ private isTrackOnCooldown;
53
+ /** Полный сброс: cooldown, журнал осцилляций и исходы переключений. */
54
+ clear(): void;
55
+ /** Журнал фиксирует отклонённое намерение, исходы не трогаются: испытательный срок не начинался. */
56
+ private rejectSwitch;
57
+ private commitSwitch;
58
+ getActiveBans(): TrackBans | undefined;
59
+ /** Телеметрия истории: статистика осцилляций и исходов переключений (у аудио outcome-контроллера нет). */
60
+ getStats(): HistoryStats;
61
+ /** Телеметрия и лог заблокированного переключения; снапшот контекста — для состояния сети/буфера. */
62
+ private reportBlockedSwitch;
63
+ resetOscillation(): void;
64
+ resetOutcome(): void;
65
+ destroy(): void;
66
+ }
67
+ export {};
@@ -0,0 +1,83 @@
1
+ import type { IBaseTrack } from "../../../../player/types";
2
+ import type { Milliseconds } from "@vkontakte/videoplayer-shared";
3
+ export declare const OSCILLATION_WINDOW_MS = 6e4;
4
+ export declare const OSCILLATION_MIN_RETURNS = 2;
5
+ export declare enum BlockReason {
6
+ TrackCooldown = "track-cooldown",
7
+ OscillationCooldown = "oscillation-cooldown"
8
+ }
9
+ export type SwitchDirection = "up" | "down";
10
+ export type SwitchEvent = {
11
+ kind: "switch";
12
+ fromId: IBaseTrack["id"];
13
+ toId: IBaseTrack["id"];
14
+ direction: SwitchDirection;
15
+ /** true — отклонённое намерение; отсутствие флага — выполненное переключение */
16
+ rejected?: boolean;
17
+ timestamp: Milliseconds;
18
+ };
19
+ export type SwitchJournalEntry = SwitchEvent | {
20
+ kind: "stallStart" | "stallEnd";
21
+ timestamp: Milliseconds;
22
+ };
23
+ export type SwitchRecordParams = {
24
+ fromId: IBaseTrack["id"];
25
+ toId: IBaseTrack["id"];
26
+ direction: SwitchDirection;
27
+ rejected?: boolean;
28
+ };
29
+ export type OscillationReturnStats = {
30
+ maxTrackReturns: number;
31
+ sumTrackReturns: number;
32
+ maxPairReturns: number;
33
+ sumPairReturns: number;
34
+ };
35
+ /**
36
+ * Осцилляция — метание качества туда-сюда (флаппинг): ABR гоняет качество
37
+ * 720p → 480p → 720p → 480p, обычно потому что оценка канала скачет вокруг границы
38
+ * двух качеств: чуть выросла — поднялись, чуть просела — упали. Вреден тем, что каждое
39
+ * переключение — это сброс загружаемых сегментов и риск буферизации, а зритель видит
40
+ * «пилящую» картинку.
41
+ *
42
+ * Владеет журналом последних событий ABR: переключения дорожек (выполненные и отклонённые
43
+ * cooldown намерения) и stall start/end. Журнал хранит порядок событий (в отличие от истории
44
+ * выборов с таймстампами) — только по порядку можно отличить флаппинг от разовых событий.
45
+ * Детектор isOscillationProven отвечает на вопрос «доказана ли осцилляция» — по нему
46
+ * AbrController решает, применять ли трековый cooldown. События stall/seek раздаёт
47
+ * HistoryController. Подробно: docs/architecture/abr/switch-journal.md
48
+ */
49
+ export declare class OscillationController {
50
+ private buffer;
51
+ private lastEntry;
52
+ recordStall(isOccurred: boolean): void;
53
+ recordSwitch({ fromId, toId, direction, rejected }: SwitchRecordParams): void;
54
+ entries(): readonly SwitchJournalEntry[];
55
+ reset(): void;
56
+ isOscillationProven(): boolean;
57
+ /**
58
+ * Телеметрия near-miss к доказательству осцилляции: немедленные откаты в окне журнала,
59
+ * просуммированные тремя способами — по возвращённой дорожке, по неупорядоченной паре
60
+ * дорожек пинг-понга и итогово (max/сумма). Показывает форму пилообразной серии там,
61
+ * где порог OSCILLATION_MIN_RETURNS ещё не взят или взят с запасом: одиночный ramp
62
+ * размазывает откаты по разным дорожкам и парам (max 1 при сумме 2), пила через разные
63
+ * вершины концентрирует откаты на одной дорожке, но на разных парах (maxTrack 2 при
64
+ * maxPair 1), пинг-понг на двух дорожках — и там и там (maxPair ≥ maxTrack).
65
+ * Решение о расширении критерия детекции принимается по этим счётчикам в A/B,
66
+ * а не по boolean isOscillationProven.
67
+ */
68
+ getStats(): OscillationReturnStats;
69
+ /**
70
+ * Считает немедленные откаты в окне осцилляции (OSCILLATION_WINDOW_MS от текущего момента).
71
+ * «Возврат» — переключение на дорожку, с которой ушли предыдущей записью, в противоположном
72
+ * направлении (A → B → A): одиночный ramp вниз-вверх (congestion без stall) не считается
73
+ * осцилляцией, а повторные откаты на ту же дорожку — считаются. Отклонённые cooldown
74
+ * намерения не считаются. Stall-запись обнуляет счётчики и разрывает смежность переключений:
75
+ * каскад до просадки и восстановление после неё — не осцилляция.
76
+ *
77
+ * Возвращает два разреза одних и тех же откатов: по дорожке и по паре (см. ImmediateReturnCounts).
78
+ */
79
+ private countImmediateReturns;
80
+ private push;
81
+ private isConsecutiveDuplicateRejectedIntent;
82
+ private dropEntriesOlderThanWindow;
83
+ }
@@ -0,0 +1,53 @@
1
+ import type { IBaseTrack } from "../../../../player/types";
2
+ import type { Milliseconds } from "@vkontakte/videoplayer-shared";
3
+ import type { ITuningConfig } from "../../../../utils/tuningConfig";
4
+ export type TrackBans = Record<IBaseTrack["id"], Milliseconds>;
5
+ /** Телеметрия исходов переключений: испытательный срок, недавние фейлы и активные баны. */
6
+ export type TrackOutcomeStats = {
7
+ /** Текущая дорожка на испытательном сроке (или просто последняя выбранная). */
8
+ currentTrackId: IBaseTrack["id"] | undefined;
9
+ /** Сколько мс текущая дорожка уже выбрана — прогресс испытательного срока. */
10
+ currentProbationMs: Milliseconds;
11
+ /** Провален ли испытательный срок текущей дорожки (stall или слишком ранний уход). */
12
+ probationFailed: boolean;
13
+ /** Число недавних фейлов в окне trackBanWindowMs по каждой дорожке. */
14
+ failsByTrack: Record<IBaseTrack["id"], number>;
15
+ /** Активные баны: id -> до какого времени. */
16
+ activeBans: TrackBans;
17
+ };
18
+ export type TrackSwitchOutcomeParams = {
19
+ fromId: IBaseTrack["id"];
20
+ toId: IBaseTrack["id"];
21
+ isUpgrade: boolean;
22
+ };
23
+ /**
24
+ * Владелец исходов переключений: испытательный срок trackProbationMs и баны за неудачные подъёмы.
25
+ * Питается ТОЛЬКО выполненными переключениями (после всех гейтов контроллера) и severe stall —
26
+ * отклонённые cooldown намерения не являются попыткой испытательного срока.
27
+ * См. docs/superpowers/specs/2026-08-30-anchor-ban-abr-design.md.
28
+ */
29
+ export declare class TrackOutcomeController {
30
+ private readonly autoTrackSelection;
31
+ private currentId;
32
+ private currentTrackSelectedAt;
33
+ private probationFailed;
34
+ private fails;
35
+ private bans;
36
+ constructor({ autoTrackSelection }: {
37
+ autoTrackSelection: ITuningConfig["autoTrackSelection"];
38
+ });
39
+ /**
40
+ * Первый трек сессии не приходит переключением (уходить не с чего), поэтому без явной
41
+ * регистрации его испытательный срок не начинается: severe stall на стартовом качестве
42
+ * и спуск с него не дают неудачи, хотя это самый частый сценарий «взяли высоко — не потянули».
43
+ */
44
+ recordInitialTrack(trackId: IBaseTrack["id"]): void;
45
+ recordSwitch({ fromId, toId, isUpgrade }: TrackSwitchOutcomeParams): void;
46
+ /** Отдаёт только ещё активные баны и подчищает истёкшие. */
47
+ getActiveBans(): TrackBans;
48
+ getStats(): TrackOutcomeStats;
49
+ reset(): void;
50
+ private finishProbation;
51
+ recordSevereStallHappened(): void;
52
+ private pushFail;
53
+ }
@@ -0,0 +1,17 @@
1
+ import type { IAbrRule, IAbrRuleResolution, IVideoAbrContext, RuleLogData } from "../../types";
2
+ import type { IVideoTrack } from "../../../../../player/types";
3
+ import { LimitAboveRule } from "../limitAboveRule";
4
+ import type { RulePhaseConfig } from "../../controllers/abrPhaseController";
5
+ type BanLogsArgs = [bannedIds: string[]];
6
+ /**
7
+ * Правило банов по истории переключений: активный banUntil (TrackOutcomeController, см. спеку
8
+ * anchor-ban) отсекает дорожку и всё выше неё — не потянули 720, не потянут и 1080. Потолок —
9
+ * старший незабаненный ниже самого низкого забаненного, поэтому всё не выше потолка (и победитель
10
+ * слияния лимитов в том числе) гарантированно незабаненное.
11
+ */
12
+ export declare class BanBasedOnHistoryRule extends LimitAboveRule<IVideoTrack, IVideoAbrContext, BanLogsArgs> implements IAbrRule<IVideoTrack, IVideoAbrContext> {
13
+ constructor(phaseConfig: RulePhaseConfig);
14
+ execute(context: IVideoAbrContext): IAbrRuleResolution<IVideoTrack>;
15
+ protected createLogData(selectedTrack: IVideoTrack, bannedIds: string[]): RuleLogData;
16
+ }
17
+ export {};
@@ -5,6 +5,7 @@ import type { Representation, Segment } from "../parsers/types";
5
5
  import type { ElementSizeManager } from "../HTMLVideoElement/ElementSizeManager";
6
6
  import type { IObservableMediaElement } from "../HTMLVideoElement/observable";
7
7
  import type { AbrPhase } from "./controllers/abrPhaseController";
8
+ import type { TrackBans } from "./controllers/trackOutcomeController";
8
9
  export type AbrLimitSources = {
9
10
  readonly above: string | undefined;
10
11
  readonly below: string | undefined;
@@ -25,7 +26,8 @@ export declare enum VideoRuleName {
25
26
  UpperLimit = "upper_limit_rule",
26
27
  Throughput = "throughput_rule",
27
28
  AbandonRequest = "abandon_request_rule",
28
- InsufficientBuffer = "insufficient_buffer_rule"
29
+ InsufficientBuffer = "insufficient_buffer_rule",
30
+ Ban = "ban_rule"
29
31
  }
30
32
  export declare enum AudioRuleName {
31
33
  MinAudioForVideo = "min_audio_for_video_rule",
@@ -92,12 +94,12 @@ export interface IAbrRule<
92
94
  C extends IBaseAbrControllerContext
93
95
  > {
94
96
  readonly activePhases: ReadonlySet<AbrPhase>;
95
- init(initContext: IRuleInitContext): void;
97
+ init(ruleContext: IRuleInitContext): void;
96
98
  destroy(): void;
97
99
  execute(context: C): IAbrRuleResolution<T>;
98
100
  }
99
101
  export interface IAbrController<T extends IBaseTrack> {
100
- init(rules: IAbrRule<T, IBaseAbrControllerContext>[], initContext: IRuleInitContext): void;
102
+ init(rules: IAbrRule<T, IBaseAbrControllerContext>[], ruleContext: IRuleInitContext): void;
101
103
  destroy(): void;
102
104
  updateContext(context: IBaseAbrControllerContext): void;
103
105
  autoSelectTrack(rules?: IAbrRule<T, IBaseAbrControllerContext>[]): T;
@@ -115,6 +117,7 @@ export interface IBaseAbrControllerContext extends IVideoTracksContext {
115
117
  forwardBufferDuration: Milliseconds | undefined;
116
118
  playbackRate: number;
117
119
  throughputVariance: number;
120
+ bannedUntil?: TrackBans;
118
121
  }
119
122
  export type VideoSegmentLoadProgress = {
120
123
  startedAtMs: Milliseconds;
@@ -7,6 +7,7 @@ export declare enum CompareTrack {
7
7
  Ascending = 1
8
8
  }
9
9
  export declare const isVideoTrack: (track: IBaseTrack) => track is IVideoTrack;
10
+ export declare const describeTrack: (track: Nullable<IBaseTrack>) => string | number | undefined;
10
11
  export declare const getAudioTrackComparator: (type: CompareTrack) => (track1: IAudioTrack, track2: IAudioTrack) => number;
11
12
  export declare const getVideoTrackComparator: (type: CompareTrack) => (track1: IVideoTrack, track2: IVideoTrack) => number;
12
13
  export declare const getTrackComparator: <T extends IBaseTrack>(compareTrack: CompareTrack) => (track1: T, track2: T) => number;