mirror of
https://github.com/LuanRT/YouTube.js.git
synced 2026-06-19 12:31:17 +00:00
feat: add support for chapters & video heatmap (#263)
* feat: add support for chapters & video heatmap * chore: add tests
This commit is contained in:
21
src/parser/classes/Chapter.ts
Normal file
21
src/parser/classes/Chapter.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import Text from './misc/Text';
|
||||
import Thumbnail from './misc/Thumbnail';
|
||||
|
||||
import { YTNode } from '../helpers';
|
||||
|
||||
class Chapter extends YTNode {
|
||||
static type = 'Chapter';
|
||||
|
||||
title: Text;
|
||||
time_range_start_millis: number;
|
||||
thumbnail: Thumbnail[];
|
||||
|
||||
constructor(data: any) {
|
||||
super();
|
||||
this.title = new Text(data.title);
|
||||
this.time_range_start_millis = data.timeRangeStartMillis;
|
||||
this.thumbnail = Thumbnail.fromResponse(data.thumbnail);
|
||||
}
|
||||
}
|
||||
|
||||
export default Chapter;
|
||||
19
src/parser/classes/DecoratedPlayerBar.ts
Normal file
19
src/parser/classes/DecoratedPlayerBar.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import Parser from '..';
|
||||
import { YTNode } from '../helpers';
|
||||
import type Button from './Button';
|
||||
import type MultiMarkersPlayerBar from './MultiMarkersPlayerBar';
|
||||
|
||||
class DecoratedPlayerBar extends YTNode {
|
||||
static type = 'DecoratedPlayerBar';
|
||||
|
||||
player_bar: MultiMarkersPlayerBar | null;
|
||||
player_bar_action_button: Button | null;
|
||||
|
||||
constructor(data: any) {
|
||||
super();
|
||||
this.player_bar = Parser.parseItem<MultiMarkersPlayerBar>(data.playerBar);
|
||||
this.player_bar_action_button = Parser.parseItem<Button>(data.playerBarActionButton);
|
||||
}
|
||||
}
|
||||
|
||||
export default DecoratedPlayerBar;
|
||||
18
src/parser/classes/HeatMarker.ts
Normal file
18
src/parser/classes/HeatMarker.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { YTNode } from '../helpers';
|
||||
|
||||
class HeatMarker extends YTNode {
|
||||
static type = 'HeatMarker';
|
||||
|
||||
time_range_start_millis: number;
|
||||
marker_duration_millis: number;
|
||||
heat_marker_intensity_score_normalized: number;
|
||||
|
||||
constructor(data: any) {
|
||||
super();
|
||||
this.time_range_start_millis = data.timeRangeStartMillis;
|
||||
this.marker_duration_millis = data.markerDurationMillis;
|
||||
this.heat_marker_intensity_score_normalized = data.heatMarkerIntensityScoreNormalized;
|
||||
}
|
||||
}
|
||||
|
||||
export default HeatMarker;
|
||||
25
src/parser/classes/Heatmap.ts
Normal file
25
src/parser/classes/Heatmap.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import Parser from '..';
|
||||
import type HeatMarker from './HeatMarker';
|
||||
|
||||
import { YTNode } from '../helpers';
|
||||
|
||||
class Heatmap extends YTNode {
|
||||
static type = 'Heatmap';
|
||||
|
||||
max_height_dp: number;
|
||||
min_height_dp: number;
|
||||
show_hide_animation_duration_millis: number;
|
||||
heat_markers: HeatMarker[];
|
||||
heat_markers_decorations: any;
|
||||
|
||||
constructor(data: any) {
|
||||
super();
|
||||
this.max_height_dp = data.maxHeightDp;
|
||||
this.min_height_dp = data.minHeightDp;
|
||||
this.show_hide_animation_duration_millis = data.showHideAnimationDurationMillis;
|
||||
this.heat_markers = Parser.parseArray<HeatMarker>(data.heatMarkers);
|
||||
this.heat_markers_decorations = Parser.parseArray(data.heatMarkersDecorations);
|
||||
}
|
||||
}
|
||||
|
||||
export default Heatmap;
|
||||
44
src/parser/classes/MultiMarkersPlayerBar.ts
Normal file
44
src/parser/classes/MultiMarkersPlayerBar.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import Parser from '..';
|
||||
import type Chapter from './Chapter';
|
||||
import type Heatmap from './Heatmap';
|
||||
|
||||
import { observe, ObservedArray, YTNode } from '../helpers';
|
||||
|
||||
class Marker extends YTNode {
|
||||
static type = 'Marker';
|
||||
|
||||
marker_key: string;
|
||||
value: {
|
||||
heatmap?: Heatmap | null;
|
||||
chapters?: Chapter[];
|
||||
};
|
||||
|
||||
constructor (data: any) {
|
||||
super();
|
||||
this.marker_key = data.key;
|
||||
|
||||
this.value = {};
|
||||
|
||||
if (data.value.heatmap) {
|
||||
this.value.heatmap = Parser.parseItem<Heatmap>(data.value.heatmap);
|
||||
}
|
||||
|
||||
if (data.value.chapters) {
|
||||
this.value.chapters = Parser.parseArray<Chapter>(data.value.chapters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MultiMarkersPlayerBar extends YTNode {
|
||||
static type = 'MultiMarkersPlayerBar';
|
||||
|
||||
markers_map: ObservedArray<Marker>;
|
||||
|
||||
constructor(data: any) {
|
||||
super();
|
||||
this.markers_map = observe(data.markersMap?.map((marker: { key: string; value: { [key: string ]: any }}) => new Marker(marker)));
|
||||
}
|
||||
}
|
||||
|
||||
export { Marker };
|
||||
export default MultiMarkersPlayerBar;
|
||||
@@ -3,6 +3,7 @@ import Menu from './menus/Menu';
|
||||
import Button from './Button';
|
||||
import WatchNextEndScreen from './WatchNextEndScreen';
|
||||
import PlayerOverlayAutoplay from './PlayerOverlayAutoplay';
|
||||
import type DecoratedPlayerBar from './DecoratedPlayerBar';
|
||||
|
||||
import { YTNode } from '../helpers';
|
||||
|
||||
@@ -16,6 +17,7 @@ class PlayerOverlay extends YTNode {
|
||||
fullscreen_engagement;
|
||||
actions;
|
||||
browser_media_session;
|
||||
decorated_player_bar;
|
||||
|
||||
constructor(data: any) {
|
||||
super();
|
||||
@@ -26,6 +28,7 @@ class PlayerOverlay extends YTNode {
|
||||
this.fullscreen_engagement = Parser.parse(data.fullscreenEngagement);
|
||||
this.actions = Parser.parseArray(data.actions);
|
||||
this.browser_media_session = Parser.parseItem(data.browserMediaSession);
|
||||
this.decorated_player_bar = Parser.parseItem<DecoratedPlayerBar>(data.decoratedPlayerBarRenderer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
23
src/parser/classes/TimedMarkerDecoration.ts
Normal file
23
src/parser/classes/TimedMarkerDecoration.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import Text from './misc/Text';
|
||||
import { YTNode } from '../helpers';
|
||||
|
||||
class TimedMarkerDecoration extends YTNode {
|
||||
static type = 'TimedMarkerDecoration';
|
||||
|
||||
visible_time_range_start_millis: number;
|
||||
visible_time_range_end_millis: number;
|
||||
decoration_time_millis: number;
|
||||
label: Text;
|
||||
icon: string;
|
||||
|
||||
constructor(data: any) {
|
||||
super();
|
||||
this.visible_time_range_start_millis = data.visibleTimeRangeStartMillis;
|
||||
this.visible_time_range_end_millis = data.visibleTimeRangeEndMillis;
|
||||
this.decoration_time_millis = data.decorationTimeMillis;
|
||||
this.label = new Text(data.label);
|
||||
this.icon = data.icon;
|
||||
}
|
||||
}
|
||||
|
||||
export default TimedMarkerDecoration;
|
||||
@@ -39,6 +39,7 @@ import { default as ChannelMobileHeader } from './classes/ChannelMobileHeader';
|
||||
import { default as ChannelOptions } from './classes/ChannelOptions';
|
||||
import { default as ChannelThumbnailWithLink } from './classes/ChannelThumbnailWithLink';
|
||||
import { default as ChannelVideoPlayer } from './classes/ChannelVideoPlayer';
|
||||
import { default as Chapter } from './classes/Chapter';
|
||||
import { default as ChildVideo } from './classes/ChildVideo';
|
||||
import { default as ChipCloud } from './classes/ChipCloud';
|
||||
import { default as ChipCloudChip } from './classes/ChipCloudChip';
|
||||
@@ -62,6 +63,7 @@ import { default as ConfirmDialog } from './classes/ConfirmDialog';
|
||||
import { default as ContinuationItem } from './classes/ContinuationItem';
|
||||
import { default as CopyLink } from './classes/CopyLink';
|
||||
import { default as CreatePlaylistDialog } from './classes/CreatePlaylistDialog';
|
||||
import { default as DecoratedPlayerBar } from './classes/DecoratedPlayerBar';
|
||||
import { default as DefaultPromoPanel } from './classes/DefaultPromoPanel';
|
||||
import { default as DidYouMean } from './classes/DidYouMean';
|
||||
import { default as DownloadButton } from './classes/DownloadButton';
|
||||
@@ -85,6 +87,8 @@ import { default as GridChannel } from './classes/GridChannel';
|
||||
import { default as GridHeader } from './classes/GridHeader';
|
||||
import { default as GridPlaylist } from './classes/GridPlaylist';
|
||||
import { default as GridVideo } from './classes/GridVideo';
|
||||
import { default as Heatmap } from './classes/Heatmap';
|
||||
import { default as HeatMarker } from './classes/HeatMarker';
|
||||
import { default as HighlightsCarousel } from './classes/HighlightsCarousel';
|
||||
import { default as HistorySuggestion } from './classes/HistorySuggestion';
|
||||
import { default as HorizontalCardList } from './classes/HorizontalCardList';
|
||||
@@ -161,6 +165,7 @@ import { default as MicroformatData } from './classes/MicroformatData';
|
||||
import { default as Mix } from './classes/Mix';
|
||||
import { default as Movie } from './classes/Movie';
|
||||
import { default as MovingThumbnail } from './classes/MovingThumbnail';
|
||||
import { default as MultiMarkersPlayerBar } from './classes/MultiMarkersPlayerBar';
|
||||
import { default as MusicCarouselShelf } from './classes/MusicCarouselShelf';
|
||||
import { default as MusicCarouselShelfBasicHeader } from './classes/MusicCarouselShelfBasicHeader';
|
||||
import { default as MusicDescriptionShelf } from './classes/MusicDescriptionShelf';
|
||||
@@ -269,6 +274,7 @@ import { default as ThumbnailOverlayResumePlayback } from './classes/ThumbnailOv
|
||||
import { default as ThumbnailOverlaySidePanel } from './classes/ThumbnailOverlaySidePanel';
|
||||
import { default as ThumbnailOverlayTimeStatus } from './classes/ThumbnailOverlayTimeStatus';
|
||||
import { default as ThumbnailOverlayToggleButton } from './classes/ThumbnailOverlayToggleButton';
|
||||
import { default as TimedMarkerDecoration } from './classes/TimedMarkerDecoration';
|
||||
import { default as TitleAndButtonListHeader } from './classes/TitleAndButtonListHeader';
|
||||
import { default as ToggleButton } from './classes/ToggleButton';
|
||||
import { default as ToggleMenuServiceItem } from './classes/ToggleMenuServiceItem';
|
||||
@@ -331,6 +337,7 @@ export const YTNodes = {
|
||||
ChannelOptions,
|
||||
ChannelThumbnailWithLink,
|
||||
ChannelVideoPlayer,
|
||||
Chapter,
|
||||
ChildVideo,
|
||||
ChipCloud,
|
||||
ChipCloudChip,
|
||||
@@ -354,6 +361,7 @@ export const YTNodes = {
|
||||
ContinuationItem,
|
||||
CopyLink,
|
||||
CreatePlaylistDialog,
|
||||
DecoratedPlayerBar,
|
||||
DefaultPromoPanel,
|
||||
DidYouMean,
|
||||
DownloadButton,
|
||||
@@ -377,6 +385,8 @@ export const YTNodes = {
|
||||
GridHeader,
|
||||
GridPlaylist,
|
||||
GridVideo,
|
||||
Heatmap,
|
||||
HeatMarker,
|
||||
HighlightsCarousel,
|
||||
HistorySuggestion,
|
||||
HorizontalCardList,
|
||||
@@ -453,6 +463,7 @@ export const YTNodes = {
|
||||
Mix,
|
||||
Movie,
|
||||
MovingThumbnail,
|
||||
MultiMarkersPlayerBar,
|
||||
MusicCarouselShelf,
|
||||
MusicCarouselShelfBasicHeader,
|
||||
MusicDescriptionShelf,
|
||||
@@ -561,6 +572,7 @@ export const YTNodes = {
|
||||
ThumbnailOverlaySidePanel,
|
||||
ThumbnailOverlayTimeStatus,
|
||||
ThumbnailOverlayToggleButton,
|
||||
TimedMarkerDecoration,
|
||||
TitleAndButtonListHeader,
|
||||
ToggleButton,
|
||||
ToggleMenuServiceItem,
|
||||
|
||||
@@ -19,10 +19,27 @@ describe('YouTube.js Tests', () => {
|
||||
expect(info.basic_info.id).toBe(VIDEOS[0].ID);
|
||||
});
|
||||
|
||||
it('should have captions on full video info', async () => {
|
||||
it('should have captions', () => {
|
||||
expect(info.captions?.caption_tracks.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should have chapters', () => {
|
||||
const markers_map = info.player_overlays?.decorated_player_bar?.player_bar?.markers_map;
|
||||
|
||||
const chapters = (
|
||||
markers_map?.get({ marker_key: 'AUTO_CHAPTERS' }) ||
|
||||
markers_map?.get({ marker_key: 'DESCRIPTION_CHAPTERS' })
|
||||
)?.value?.chapters;
|
||||
|
||||
expect(chapters).toBeDefined();
|
||||
});
|
||||
|
||||
it('should have heatmap', () => {
|
||||
const markers_map = info.player_overlays?.decorated_player_bar?.player_bar?.markers_map;
|
||||
const heatmap = markers_map?.get({ marker_key: 'HEATSEEKER' })?.value?.heatmap;
|
||||
expect(heatmap).toBeDefined();
|
||||
});
|
||||
|
||||
it('should retrieve basic video info', async () => {
|
||||
const b_info = await yt.getBasicInfo(VIDEOS[0].ID);
|
||||
expect(b_info.basic_info.id).toBe(VIDEOS[0].ID);
|
||||
@@ -114,9 +131,6 @@ describe('YouTube.js Tests', () => {
|
||||
|
||||
const filtered_list = await videos_tab.applyFilter('Popular');
|
||||
expect(filtered_list.videos.length).toBeGreaterThan(0);
|
||||
|
||||
const search = await channel.search('e-ink');
|
||||
expect(search.videos.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should retrieve home feed', async () => {
|
||||
|
||||
Reference in New Issue
Block a user