Compare commits

...
Sign in to create a new pull request.

20 commits

Author SHA1 Message Date
01c6a60624
WIP 2025-07-06 17:28:32 +02:00
d8d6830dfb
wip: add sabr_context_update 2025-06-30 00:02:31 +02:00
51c0d54ddb
WIP 2025-06-30 00:02:30 +02:00
ta3pks
48bb1b95d5
remove unwrap trying to fetch visitor data (#60)
Co-authored-by: nikos efthias <nikos@mugsoft.io>
Reviewed-on: https://codeberg.org/ThetaDev/rustypipe/pulls/60
Co-authored-by: ta3pks <ta3pks@noreply.codeberg.org>
Co-committed-by: ta3pks <ta3pks@noreply.codeberg.org>
2025-06-30 00:02:30 +02:00
ad75c71727
docs: abr streams stabilized 2025-04-29 12:46:53 +02:00
04a89c8159
docs: abr streams with 12% probability 2025-04-28 14:54:06 +02:00
043dc006ba
docs: abr streams with 25% probability 2025-04-26 00:36:18 +02:00
5b183f2b74
docs: abr streams with 8% probability 2025-04-23 21:35:15 +02:00
86c5bf8ef5
docs: abr streams discontinued 2025-04-23 21:35:15 +02:00
43b4f71a1b
docs: abr streams now returned with 2% probability 2025-04-23 21:35:14 +02:00
e759738c00
feat: add allow_abr_only option 2025-04-23 21:35:14 +02:00
a389648a4b
docs: abr streams now returned with 10% probability 2025-04-23 21:35:14 +02:00
69fb82e39f
test: update player data snapshots 2025-04-23 21:35:14 +02:00
2f3c01cb28
wip 2025-04-23 21:35:13 +02:00
cae99513f6
fix: A/B test 23 check 2025-04-23 21:35:13 +02:00
1c2db9b88d
Revert "fix: handle player returning no adaptive stream URLs"
This reverts commit 07db7b1166.
2025-04-23 21:35:13 +02:00
e9fc556d89
wip: remove unused code 2025-04-23 21:35:12 +02:00
f4f3e4f256
wip: working prototype 2025-04-23 21:35:12 +02:00
3a41899ce7
feat: add rustypipe-abr-proto crate 2025-04-23 21:35:12 +02:00
2ecb4b6d3f
feat!: extract ABR streaming URL, make audio/video stream url optional 2025-04-23 21:35:12 +02:00
67 changed files with 5465 additions and 170 deletions

4
.gitignore vendored
View file

@ -6,3 +6,7 @@
rustypipe_reports
rustypipe_cache*.json
bg_snapshot.bin
*.webm
*.mp4
*.m4a

View file

@ -13,7 +13,7 @@ description = "Client for the public YouTube / YouTube Music API (Innertube), in
include = ["/src", "README.md", "CHANGELOG.md", "LICENSE", "!snapshots"]
[workspace]
members = [".", "codegen", "downloader", "cli"]
members = [".", "codegen", "downloader", "cli", "abr-proto"]
[workspace.package]
edition = "2021"
@ -56,6 +56,7 @@ urlencoding = "2.1.0"
quick-xml = { version = "0.37.0", features = ["serialize"] }
tracing = { version = "0.1.0", features = ["log"] }
localzone = "0.3.1"
protobuf = "3.0.0"
# CLI
indicatif = "0.17.0"
@ -79,6 +80,7 @@ rustypipe-downloader = { path = "./downloader", version = "0.3.1", default-featu
"indicatif",
"audiotag",
] }
rustypipe-abr-proto = { path = "./abr-proto", version = "0.1.0" }
[features]
default = ["default-tls"]

16
abr-proto/Cargo.toml Normal file
View file

@ -0,0 +1,16 @@
[package]
name = "rustypipe-abr-proto"
version = "0.1.0"
edition.workspace = true
authors.workspace = true
license = "MIT"
repository.workspace = true
keywords.workspace = true
categories.workspace = true
description = "Protobuf definitions for YouTube adaptive bitrate streaming"
[dependencies]
protobuf.workspace = true
[build-dependencies]
protobuf-codegen = "3"

21
abr-proto/LICENSE Normal file
View file

@ -0,0 +1,21 @@
Copyright (c) 2025 ThetaDev
MIT LICENSE
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

5
abr-proto/README.md Normal file
View file

@ -0,0 +1,5 @@
# ![RustyPipe](https://codeberg.org/ThetaDev/rustypipe/raw/branch/main/notes/logo.svg) abr-proto
[![Current crates.io version](https://img.shields.io/crates/v/rustypipe-abr-proto.svg)](https://crates.io/crates/rustypipe-abr-proto)
[![License](https://img.shields.io/badge/License-MIT-blue.svg?style=flat)](https://opensource.org/licenses/MIT)
[![CI status](https://codeberg.org/ThetaDev/rustypipe/actions/workflows/ci.yaml/badge.svg?style=flat&label=CI)](https://codeberg.org/ThetaDev/rustypipe/actions/?workflow=ci.yaml)

68
abr-proto/build.rs Normal file
View file

@ -0,0 +1,68 @@
use std::{
env, fs,
ops::Deref,
path::{Path, PathBuf},
};
fn out_dir() -> PathBuf {
Path::new(&env::var("OUT_DIR").expect("env")).to_path_buf()
}
fn cleanup() {
let _ = fs::remove_dir_all(out_dir());
}
fn compile() {
let proto_dir = Path::new(&env::var("CARGO_MANIFEST_DIR").expect("env")).join("proto");
let vsdir = proto_dir.join("video_streaming");
let files = &[
proto_dir.join("misc").join("common.proto"),
vsdir.join("buffered_range.proto"),
vsdir.join("client_abr_state.proto"),
vsdir.join("crypto_params.proto"),
vsdir.join("encrypted_player_request.proto"),
vsdir.join("format_initialization_metadata.proto"),
vsdir.join("live_metadata.proto"),
vsdir.join("media_capabilities.proto"),
vsdir.join("media_header.proto"),
vsdir.join("next_request_policy.proto"),
vsdir.join("onesie_header_type.proto"),
vsdir.join("onesie_header.proto"),
vsdir.join("onesie_player_request.proto"),
vsdir.join("onesie_player_response.proto"),
vsdir.join("onesie_request.proto"),
vsdir.join("playback_cookie.proto"),
vsdir.join("playback_start_policy.proto"),
vsdir.join("proxy_status.proto"),
vsdir.join("reload_player_response.proto"),
vsdir.join("request_cancellation_policy.proto"),
vsdir.join("sabr_error.proto"),
vsdir.join("sabr_redirect.proto"),
vsdir.join("stream_protection_status.proto"),
vsdir.join("streamer_context.proto"),
vsdir.join("time_range.proto"),
vsdir.join("video_playback_abr_request.proto"),
vsdir.join("sabr_context_update.proto"),
vsdir.join("sabr_context_sending_policy.proto"),
vsdir.join("sabr_seek.proto"),
];
let slices = files.iter().map(Deref::deref).collect::<Vec<_>>();
let out_dir = out_dir();
fs::create_dir(&out_dir).expect("create_dir");
protobuf_codegen::Codegen::new()
.pure()
.out_dir(&out_dir)
.inputs(&slices)
.include(&proto_dir)
.run()
.expect("Codegen failed.");
}
fn main() {
cleanup();
compile();
}

View file

@ -0,0 +1,28 @@
syntax = "proto2";
package misc;
message HttpHeader {
optional string name = 1;
optional string value = 2;
}
message FormatId {
optional int32 itag = 1;
optional uint64 last_modified = 2;
optional string xtags = 3;
}
message InitRange {
optional int32 start = 1;
optional int32 end = 2;
}
message IndexRange {
optional int32 start = 1;
optional int32 end = 2;
}
message KeyValuePair {
optional string key = 1;
optional string value = 2;
}

View file

@ -0,0 +1,32 @@
syntax = "proto2";
package video_streaming;
import "misc/common.proto";
import "video_streaming/time_range.proto";
message BufferedRange {
required .misc.FormatId format_id = 1;
required int64 start_time_ms = 2;
required int64 duration_ms = 3;
required int32 start_segment_index = 4;
required int32 end_segment_index = 5;
optional TimeRange time_range = 6;
optional Kob field9 = 9;
optional YPa field11 = 11;
optional YPa field12 = 12;
}
message Kob {
message Pa {
optional string video_id = 1;
optional uint64 lmt = 2;
}
repeated Pa EW = 1;
}
message YPa {
optional int32 field1 = 1;
optional int32 field2 = 2;
optional int32 field3 = 3;
}

View file

@ -0,0 +1,44 @@
syntax = "proto2";
package video_streaming;
message ClientAbrState {
optional int64 time_since_last_manual_format_selection_ms = 13;
optional int32 last_manual_direction = 14;
optional int32 last_manual_selected_resolution = 16;
optional int32 detailed_network_type = 17;
optional int32 client_viewport_width = 18;
optional int32 client_viewport_height = 19;
optional int64 client_bitrate_cap = 20;
optional int32 sticky_resolution = 21;
optional bool client_viewport_is_flexible = 22;
optional int32 bandwidth_estimate = 23;
optional int64 player_time_ms = 28;
optional int64 time_since_last_seek = 29;
optional bool data_saver_mode = 30;
optional int32 visibility = 34;
optional float playback_rate = 35;
optional int64 elapsed_wall_time_ms = 36;
optional bytes media_capabilities = 38;
optional int64 time_since_last_action_ms = 39;
optional int32 enabled_track_types_bitfield = 40;
optional int32 max_pacing_rate = 43;
optional int64 player_state = 44;
optional bool drc_enabled = 46;
optional int32 Jda = 48;
optional int32 qw = 50;
optional int32 Ky = 51;
optional int32 sabr_report_request_cancellation_info = 54;
optional bool l = 56;
optional int64 G7 = 57;
optional bool prefer_vp9 = 58;
optional int32 qj = 59;
optional int32 Hx = 60;
optional bool is_prefetch = 61;
optional int32 sabr_support_quality_constraints = 62;
optional bytes sabr_license_constraint = 63;
optional int32 allow_proxima_live_latency = 64;
optional int32 sabr_force_proxima = 66;
optional int32 Tqb = 67;
optional int64 sabr_force_max_network_interruption_duration_ms = 68;
optional string audio_track_id = 69;
}

View file

@ -0,0 +1,13 @@
syntax = "proto2";
package video_streaming;
message CryptoParams {
enum CompressionType {
VAL_0 = 0;
VAL_1 = 1;
VAL_2 = 2;
}
optional bytes hmac = 4;
optional bytes iv = 5;
optional CompressionType compression_type = 6;
}

View file

@ -0,0 +1,18 @@
syntax = "proto2";
package video_streaming;
import "video_streaming/onesie_player_request.proto";
message EncryptedPlayerRequest {
optional bytes context = 1; // InnerTubeContext proto?
optional bytes encrypted_onesie_player_request = 2;
optional bytes encrypted_client_key = 5;
optional bytes iv = 6;
optional bytes hmac = 7;
optional string reverse_proxy_config = 9;
optional bool serialize_response_as_json = 10;
optional bool pM = 13;
optional bool enable_compression = 14;
optional bytes unencrypted_onesie_player_request = 16;
optional bool TQ = 17;
}

View file

@ -0,0 +1,17 @@
syntax = "proto2";
package video_streaming;
import "misc/common.proto";
message FormatInitializationMetadata {
optional string video_id = 1;
optional .misc.FormatId format_id = 2;
optional int32 end_time_ms = 3;
optional int32 sequence_count = 4;
optional string mime_type = 5;
optional .misc.InitRange init_range = 6;
optional .misc.IndexRange index_range = 7;
optional int32 field8 = 8;
optional int32 duration_ms = 9;
optional int32 field10 = 10;
}

View file

@ -0,0 +1,13 @@
syntax = "proto2";
package video_streaming;
message LiveMetadata {
optional uint32 head_sequence_number = 3;
optional uint64 head_time_ms = 4;
optional uint64 wall_time_ms = 5;
optional uint64 field10 = 10;
optional uint64 field12 = 12;
optional uint64 field13 = 13;
optional uint64 head_time_usec = 14;
optional uint64 field15 = 15;
}

View file

@ -0,0 +1,24 @@
syntax = "proto2";
package video_streaming;
message MediaCapabilities {
repeated VideoFormatCapability video_format_capabilities = 1;
repeated AudioFormatCapability audio_format_capabilities = 2;
optional int32 hdr_mode_bitmask = 5;
message VideoFormatCapability {
optional int32 video_codec = 1;
optional int32 max_height = 3;
optional int32 max_width = 4;
optional int32 max_framerate = 11;
optional int32 max_bitrate_bps = 12;
optional bool is_10_bit_supported = 15;
}
message AudioFormatCapability {
optional int32 audio_codec = 1;
optional int32 num_channels = 2;
optional int32 max_bitrate_bps = 3;
optional int32 spatial_capability_bitmask = 6;
}
}

View file

@ -0,0 +1,29 @@
syntax = "proto2";
package video_streaming;
import "misc/common.proto";
import "video_streaming/time_range.proto";
message MediaHeader {
optional uint32 header_id = 1;
optional string video_id = 2;
optional int32 itag = 3;
optional uint64 lmt = 4;
optional string xtags = 5;
optional int32 start_data_range = 6;
optional Compression compression = 7;
optional bool is_init_seg = 8;
optional int64 sequence_number = 9;
optional int64 field10 = 10;
optional int32 start_ms = 11;
optional int32 duration_ms = 12;
optional .misc.FormatId format_id = 13;
optional int64 content_length = 14;
optional TimeRange time_range = 15;
enum Compression {
UNKNOWN = 0;
NONE = 1;
GZIP = 2;
}
}

View file

@ -0,0 +1,12 @@
syntax = "proto2";
package video_streaming;
import "video_streaming/playback_cookie.proto";
message NextRequestPolicy {
optional int32 target_audio_readahead_ms = 1;
optional int32 target_video_readahead_ms = 2;
optional int32 backoff_time_ms = 4;
optional .video_streaming.PlaybackCookie playback_cookie = 7;
optional string video_id = 8;
}

View file

@ -0,0 +1,27 @@
syntax = "proto2";
package video_streaming;
import "video_streaming/onesie_header_type.proto";
import "video_streaming/crypto_params.proto";
message OnesieHeader {
message Field23 {
optional string video_id = 2;
}
message Field34 {
repeated string itag_denylist = 1;
}
optional OnesieHeaderType type = 1;
optional string video_id = 2;
optional string itag = 3;
optional CryptoParams crypto_params = 4;
optional uint64 last_modified = 5;
optional int64 media_size_bytes = 7;
repeated string restricted_formats = 11;
optional string xtags = 15;
optional int64 sequence_number = 18;
optional Field23 field23 = 23;
optional Field34 field34 = 34;
}

View file

@ -0,0 +1,31 @@
syntax = "proto2";
package video_streaming;
enum OnesieHeaderType {
PLAYER_RESPONSE = 0;
HEADER_TYPE_1 = 1;
MEDIA_DECRYPTION_KEY = 2;
HEADER_TYPE_3 = 3;
HEADER_TYPE_4 = 4;
HEADER_TYPE_5 = 5;
NEW_HOST = 6;
HEADER_TYPE_7 = 7;
HEADER_TYPE_8 = 8;
HEADER_TYPE_9 = 9;
HEADER_TYPE_10 = 10;
HEADER_TYPE_11 = 11;
HEADER_TYPE_12 = 12;
HEADER_TYPE_13 = 13;
RESTRICTED_FORMATS_HINT = 14;
HEADER_TYPE_15 = 15;
STREAM_METADATA = 16;
HEADER_TYPE_17 = 17;
HEADER_TYPE_18 = 18;
HEADER_TYPE_19 = 19;
HEADER_TYPE_20 = 20;
HEADER_TYPE_21 = 21;
HEADER_TYPE_22 = 22;
HEADER_TYPE_23 = 23;
HEADER_TYPE_24 = 24;
ENCRYPTED_INNERTUBE_RESPONSE_PART = 25;
}

View file

@ -0,0 +1,12 @@
syntax = "proto2";
package video_streaming;
import "misc/common.proto";
message OnesiePlayerRequest {
optional string url = 1;
repeated misc.HttpHeader headers = 2;
optional string body = 3;
optional bool proxied_by_trusted_bandaid = 4;
optional bool skip_response_encryption = 6;
}

View file

@ -0,0 +1,28 @@
syntax = "proto2";
package video_streaming;
import "misc/common.proto";
enum OnesieProxyStatus {
ONESIE_PROXY_STATUS_UNKNOWN = 0;
ONESIE_PROXY_STATUS_OK = 1;
ONESIE_PROXY_STATUS_DECRYPTION_FAILED = 2;
ONESIE_PROXY_STATUS_PARSING_FAILED = 3;
ONESIE_PROXY_STATUS_MISSING_X_FORWARDED_FOR = 4;
ONESIE_PROXY_STATUS_INVALID_X_FORWARDED_FOR = 5;
ONESIE_PROXY_STATUS_INVALID_CONTENT_TYPE = 6;
ONESIE_PROXY_STATUS_BACKEND_ERROR = 7;
ONESIE_PROXY_STATUS_CLIENT_ERROR = 8;
ONESIE_PROXY_STATUS_MISSING_CRYPTER = 9;
ONESIE_PROXY_STATUS_RESPONSE_JSON_SERIALIZATION_FAILED = 10;
ONESIE_PROXY_STATUS_DECOMPRESSION_FAILED = 11;
ONESIE_PROXY_STATUS_JSON_PARSING_FAILED = 12;
ONESIE_PROXY_STATUS_UNKNOWN_COMPRESSION_TYPE = 13;
}
message OnesiePlayerResponse {
optional OnesieProxyStatus onesie_proxy_status = 1;
optional int32 http_status = 2;
repeated .misc.HttpHeader headers = 3;
optional bytes body = 4;
}

View file

@ -0,0 +1,19 @@
syntax = "proto2";
package video_streaming;
import "video_streaming/client_abr_state.proto";
import "video_streaming/encrypted_player_request.proto";
import "video_streaming/streamer_context.proto";
import "video_streaming/buffered_range.proto";
message OnesieRequest {
repeated string urls = 1;
optional ClientAbrState client_abr_state = 2;
optional EncryptedPlayerRequest player_request = 3;
optional bytes onesie_ustreamer_config = 4;
optional int32 max_vp9_height = 5;
optional int32 client_display_height = 6;
optional StreamerContext streamer_context = 10;
optional int32 request_target = 13; // MLOnesieRequestTarget
repeated BufferedRange buffered_ranges = 14;
}

View file

@ -0,0 +1,11 @@
syntax = "proto2";
package video_streaming;
import "misc/common.proto";
message PlaybackCookie {
optional int32 field1 = 1; // Always 999999??
optional int32 field2 = 2;
optional .misc.FormatId video_fmt = 7;
optional .misc.FormatId audio_fmt = 8;
}

View file

@ -0,0 +1,12 @@
syntax = "proto2";
package video_streaming;
message PlaybackStartPolicy {
message ReadaheadPolicy {
optional int32 min_readahead_ms = 2;
optional int32 min_bandwidth_bytes_per_sec = 1;
}
optional ReadaheadPolicy start_min_readahead_policy = 1;
optional ReadaheadPolicy resume_min_readahead_policy = 2;
}

View file

@ -0,0 +1,15 @@
syntax = "proto2";
package video_streaming;
enum ProxyStatus {
VAL_0 = 0;
OK = 1;
VAL_2 = 2;
VAL_3 = 3;
VAL_4 = 4;
VAL_5 = 5;
VAL_6 = 6;
VAL_7 = 7;
VAL_8 = 8;
VAL_9 = 9;
}

View file

@ -0,0 +1,10 @@
syntax = "proto2";
package video_streaming;
message ReloadPlaybackParams {
optional string token = 1;
}
message ReloadPlayerResponse {
optional ReloadPlaybackParams reload_playback_params = 1;
}

View file

@ -0,0 +1,14 @@
syntax = "proto2";
package video_streaming;
message RequestCancellationPolicy {
message Item {
optional int32 fR = 1;
optional int32 NK = 2;
optional int32 minReadaheadMs = 3;
}
optional int32 N0 = 1;
repeated Item items = 2;
optional int32 jq = 3;
}

View file

@ -0,0 +1,5 @@
message SabrContextSendingPolicy {
repeated int32 start_policy = 1;
repeated int32 stop_policy = 2;
repeated int32 discard_policy = 3;
}

View file

@ -0,0 +1,21 @@
message SabrContextUpdate {
enum SabrContextScope {
SABR_CONTEXT_SCOPE_UNKNOWN = 0;
SABR_CONTEXT_SCOPE_PLAYBACK = 1;
SABR_CONTEXT_SCOPE_REQUEST = 2;
SABR_CONTEXT_SCOPE_WATCH_ENDPOINT = 3;
SABR_CONTEXT_SCOPE_CONTENT_ADS = 4;
}
enum SabrContextWritePolicy {
SABR_CONTEXT_WRITE_POLICY_UNSPECIFIED = 0;
SABR_CONTEXT_WRITE_POLICY_OVERWRITE = 1;
SABR_CONTEXT_WRITE_POLICY_KEEP_EXISTING = 2;
}
optional int32 type = 1;
optional SabrContextScope scope = 2;
optional bytes value = 3;
optional bool send_by_default = 4;
optional SabrContextWritePolicy write_policy = 5;
}

View file

@ -0,0 +1,7 @@
syntax = "proto2";
package video_streaming;
message SabrError {
optional string type = 1;
optional int32 code = 2;
}

View file

@ -0,0 +1,6 @@
syntax = "proto2";
package video_streaming;
message SabrRedirect {
optional string url = 1;
}

View file

@ -0,0 +1,18 @@
message SabrSeek {
enum SeekSource {
SEEK_SOURCE_UNKNOWN = 0;
SEEK_SOURCE_SABR_PARTIAL_CHUNK = 9;
SEEK_SOURCE_SABR_SEEK_TO_HEAD = 10;
SEEK_SOURCE_SABR_LIVE_DVR_USER_SEEK = 11;
SEEK_SOURCE_SABR_SEEK_TO_DVR_LOWER_BOUND = 12;
SEEK_SOURCE_SABR_SEEK_TO_DVR_UPPER_BOUND = 13;
SEEK_SOURCE_SABR_ACCURATE_SEEK = 17;
SEEK_SOURCE_SABR_INGESTION_WALL_TIME_SEEK = 29;
SEEK_SOURCE_SABR_SEEK_TO_CLOSEST_KEYFRAME = 59;
SEEK_SOURCE_SABR_RELOAD_PLAYER_RESPONSE_TOKEN_SEEK = 106;
}
optional int32 seek_time_ticks = 1;
optional int32 timescale = 2;
optional SeekSource seek_source = 3;
}

View file

@ -0,0 +1,7 @@
syntax = "proto2";
package video_streaming;
message StreamProtectionStatus {
optional int32 status = 1;
optional int32 field2 = 2;
}

View file

@ -0,0 +1,66 @@
syntax = "proto2";
package video_streaming;
message StreamerContext {
message ClientInfo {
optional string device_make = 12;
optional string device_model = 13;
optional int32 client_name = 16;
optional string client_version = 17;
optional string os_name = 18;
optional string os_version = 19;
optional string accept_language = 21;
optional string accept_region = 22;
optional int32 screen_width_points = 37;
optional int32 screen_height_points = 38;
optional float screen_width_inches = 39;
optional float screen_height_inches = 40;
optional int32 screen_pixel_density = 41;
optional ClientFormFactor client_form_factor = 46;
optional int32 gmscore_version_code = 50; // e.g. 243731017
optional int32 window_width_points = 55;
optional int32 window_height_points = 56;
optional int32 android_sdk_version = 64;
optional float screen_density_float = 65;
optional int64 utc_offset_minutes = 67;
optional string time_zone = 80;
optional string chipset = 92; // e.g. "qcom;taro"
optional GLDeviceInfo gl_device_info = 102;
}
enum ClientFormFactor {
UNKNOWN_FORM_FACTOR = 0;
FORM_FACTOR_VAL1 = 1;
FORM_FACTOR_VAL2 = 2;
}
message GLDeviceInfo {
optional string gl_renderer = 1;
optional int32 gl_es_version_major = 2;
optional int32 gl_es_version_minor = 3;
}
message SabrContext {
optional int32 type = 1;
optional bytes value = 2;
}
message Gqa {
message Hqa {
optional int32 code = 1;
optional string message = 2;
}
optional bytes field1 = 1;
optional Hqa field2 = 2;
}
optional ClientInfo client_info = 1;
optional bytes po_token = 2;
optional bytes playback_cookie = 3;
optional bytes gp = 4;
repeated SabrContext sabr_contexts = 5;
repeated int32 unsent_sabr_contexts = 6;
optional string field7 = 7;
optional Gqa field8 = 8;
}

View file

@ -0,0 +1,8 @@
syntax = "proto2";
package video_streaming;
message TimeRange {
optional int64 start = 1;
optional int64 duration = 2;
optional int32 timescale = 3;
}

View file

@ -0,0 +1,51 @@
syntax = "proto2";
package video_streaming;
import "misc/common.proto";
import "video_streaming/client_abr_state.proto";
import "video_streaming/streamer_context.proto";
import "video_streaming/buffered_range.proto";
message VideoPlaybackAbrRequest {
optional ClientAbrState client_abr_state = 1;
repeated .misc.FormatId selected_format_ids = 2;
repeated BufferedRange buffered_ranges = 3;
optional int64 player_time_ms = 4;
optional bytes video_playback_ustreamer_config = 5;
optional Lo lo = 6;
repeated .misc.FormatId selected_audio_format_ids = 16;
repeated .misc.FormatId selected_video_format_ids = 17;
optional StreamerContext streamer_context = 19;
optional OQa field21 = 21;
optional int32 field22 = 22;
optional int32 field23 = 23;
repeated Pqa field1000 = 1000;
}
message Lo {
message Field4 {
optional int32 field1 = 1;
optional int32 field2 = 2;
optional int32 field3 = 3;
}
optional .misc.FormatId format_id = 1;
optional int32 Lj = 2;
optional int32 sequence_number = 3;
optional Field4 field4 = 4;
optional int32 MZ = 5;
}
message OQa {
repeated string field1 = 1;
optional bytes field2 = 2;
optional string field3 = 3;
optional int32 field4 = 4;
optional int32 field5 = 5;
optional string field6 = 6;
}
message Pqa {
repeated .misc.FormatId formats = 1;
repeated BufferedRange ud = 2;
optional string clip_id = 3;
}

3
abr-proto/src/lib.rs Normal file
View file

@ -0,0 +1,3 @@
#![doc = include_str!("../README.md")]
include!(concat!(env!("OUT_DIR"), "/mod.rs"));

View file

@ -42,6 +42,7 @@ pub enum ABTest {
MusicContinuationItemRenderer = 20,
AlbumRecommends = 21,
CommandExecutorCommand = 22,
AbrStreamOnly = 23,
}
/// List of active A/B tests that are run when none is manually specified
@ -49,6 +50,7 @@ const TESTS_TO_RUN: &[ABTest] = &[
ABTest::MusicAlbumGroupsReordered,
ABTest::AlbumRecommends,
ABTest::CommandExecutorCommand,
ABTest::AbrStreamOnly,
];
#[derive(Debug, Serialize, Deserialize)]
@ -126,6 +128,7 @@ pub async fn run_test(
}
ABTest::AlbumRecommends => album_recommends(&query).await,
ABTest::CommandExecutorCommand => command_executor_command(&query).await,
ABTest::AbrStreamOnly => abr_stream_only(&query).await,
}
.unwrap();
pb.inc(1);
@ -478,3 +481,12 @@ pub async fn command_executor_command(rp: &RustyPipeQuery) -> Result<bool> {
.await?;
Ok(res.contains("\"commandExecutorCommand\""))
}
pub async fn abr_stream_only(rp: &RustyPipeQuery) -> Result<bool> {
let id = "pxY4OXVyMe4";
let res = rp
.clone()
.player_from_client(id, ClientType::Desktop)
.await?;
Ok(res.video_only_streams.iter().all(|s| s.url.is_none()))
}

View file

@ -34,13 +34,14 @@ audiotag = ["dep:lofty", "dep:image", "dep:smartcrop2"]
[dependencies]
rustypipe.workspace = true
rustypipe-abr-proto.workspace = true
once_cell.workspace = true
regex.workspace = true
thiserror.workspace = true
futures-util.workspace = true
reqwest = { workspace = true, features = ["stream"] }
rand.workspace = true
tokio = { workspace = true, features = ["macros", "fs", "process"] }
tokio = { workspace = true, features = ["macros", "fs", "process", "rt-multi-thread"] }
indicatif = { workspace = true, optional = true }
filenamify.workspace = true
tracing.workspace = true
@ -52,10 +53,21 @@ image = { version = "0.25.0", optional = true, default-features = false, feature
"webp",
] }
smartcrop2 = { version = "0.4.0", optional = true }
data-encoding.workspace = true
protobuf.workspace = true
bytes = "1.0.0"
byteorder = "1.0.0"
async-stream = "0.3.6"
pin-project-lite = "0.2.11"
serde.workspace = true
serde_json.workspace = true
tracing-subscriber.workspace = true
[dev-dependencies]
path_macro.workspace = true
rstest.workspace = true
tracing-test.workspace = true
serde_json.workspace = true
temp_testdir = "0.2.3"

676
downloader/src/abr.rs Normal file
View file

@ -0,0 +1,676 @@
#![allow(unused)]
use std::collections::{HashMap, HashSet};
use bytes::Bytes;
use data_encoding::BASE64URL;
use protobuf::{Message, MessageField};
use reqwest::Client;
use rustypipe::model::{AudioStream, VideoStream};
pub use rustypipe_abr_proto::client_abr_state::ClientAbrState;
use rustypipe_abr_proto::{
buffered_range::BufferedRange,
common::FormatId,
format_initialization_metadata::FormatInitializationMetadata,
media_header::MediaHeader,
next_request_policy::NextRequestPolicy,
playback_cookie::PlaybackCookie,
sabr_error::SabrError,
sabr_redirect::SabrRedirect,
stream_protection_status::StreamProtectionStatus,
streamer_context::{streamer_context::ClientInfo, StreamerContext},
time_range::TimeRange,
video_playback_abr_request::VideoPlaybackAbrRequest,
};
use crate::abr_model::PartType;
use crate::error::AbrError;
pub struct AbrStream {
http: Client,
initial_url: String,
ustreamer_config: Vec<u8>,
po_token: Option<Vec<u8>>,
}
#[derive(Debug)]
pub struct StreamingState {
abr_streaming_url: String,
abr_state: ClientAbrState,
playback_cookie: Option<PlaybackCookie>,
backoff_time_ms: i32,
formats_by_key: HashMap<String, InitializedFormat>,
header_id_to_format_key_map: HashMap<u8, String>,
previous_sequences: HashMap<String, HashSet<i64>>,
}
struct InitializedFormat {
format_id: FormatId,
duration_ms: Option<i32>,
mime_type: Option<String>,
sequence_count: Option<i32>,
sequence_list: Vec<Sequence>,
media_chunks: Vec<u8>,
state: BufferedRange,
}
#[derive(Debug)]
struct Sequence {
itag: Option<i32>,
format_id: FormatId,
is_init_segment: bool,
duration_ms: Option<i32>,
start_ms: Option<i32>,
start_data_range: Option<i32>,
sequence_number: Option<i64>,
content_length: Option<i64>,
time_range: Option<TimeRange>,
}
struct UmpPart<'a> {
part_type: PartType,
size: u64,
data: &'a [u8],
}
impl std::fmt::Debug for InitializedFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("InitializedFormat")
.field("format_id", &self.format_id)
.field("duration_ms", &self.duration_ms)
.field("mime_type", &self.mime_type)
.field("sequence_count", &self.sequence_count)
.field("sequence_list", &self.sequence_list)
.field("media_chunks", &self.media_chunks.len())
.field("state", &self.state)
.finish()
}
}
impl std::fmt::Debug for UmpPart<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UmpPart")
.field("part_type", &self.part_type)
.field("size", &self.size)
.field("data", &self.data.len())
.finish()
}
}
impl AbrStream {
pub fn new(
url: String,
ustreamer_config: &str,
po_token: Option<&str>,
http: Client,
) -> Result<Self, AbrError> {
let ustreamer_config = BASE64URL.decode(ustreamer_config.as_bytes()).map_err(|e| {
AbrError::Invalid(format!("could not parse ustreamer_config: {e}").into())
})?;
let po_token =
match po_token {
Some(pot) => Some(BASE64URL.decode(pot.as_bytes()).map_err(|e| {
AbrError::Invalid(format!("could not parse po_token: {e}").into())
})?),
None => None,
};
Ok(Self {
http,
initial_url: url,
ustreamer_config,
po_token,
})
}
pub async fn init(
&self,
audio_streams: &[&AudioStream],
video_streams: &[&VideoStream],
mut abr_state: ClientAbrState,
) -> Result<StreamingState, AbrError> {
if !abr_state.has_last_manual_direction() {
abr_state.set_last_manual_direction(0);
}
if !abr_state.has_time_since_last_manual_format_selection_ms() {
abr_state.set_time_since_last_manual_format_selection_ms(0);
}
if !abr_state.has_player_time_ms() {
abr_state.set_player_time_ms(0);
}
if !abr_state.has_visibility() {
abr_state.set_visibility(0);
}
if !abr_state.has_enabled_track_types_bitfield() {
abr_state.set_enabled_track_types_bitfield(i32::from(video_streams.is_empty()));
}
if let Some(v) = video_streams.first() {
if !abr_state.has_last_manual_selected_resolution() {
abr_state.set_last_manual_selected_resolution(v.height as i32);
}
if !abr_state.has_sticky_resolution() {
abr_state.set_sticky_resolution(v.height as i32);
}
}
let audio_format_ids = audio_streams
.iter()
.map(|s| FormatId {
itag: Some(s.itag as i32),
last_modified: s.last_modified,
xtags: s.xtags.clone(),
..Default::default()
})
.collect::<Vec<_>>();
let video_format_ids = video_streams
.iter()
.map(|s| FormatId {
itag: Some(s.itag as i32),
last_modified: s.last_modified,
xtags: s.xtags.clone(),
..Default::default()
})
.collect::<Vec<_>>();
let mut state = StreamingState::new(self.initial_url.to_owned(), abr_state);
let mut ri = 0;
loop {
let data = self
.fetch_media(audio_format_ids.clone(), video_format_ids.clone(), &state)
.await?;
tracing::debug!("request #{ri} fetched {} bytes", data.len());
let ump_parts = Self::parse_ump(&data);
if state.process_ump_parts(&ump_parts)? {
break;
}
ri += 1;
}
Ok(state)
}
async fn fetch_media(
&self,
audio_format_ids: Vec<FormatId>,
video_format_ids: Vec<FormatId>,
state: &StreamingState,
) -> Result<Bytes, AbrError> {
let body = VideoPlaybackAbrRequest {
client_abr_state: MessageField::some(state.abr_state.clone()),
selected_format_ids: state
.formats_by_key
.values()
.map(|f| f.format_id.clone())
.collect(),
buffered_ranges: state
.formats_by_key
.values()
.map(|f| f.state.clone())
.collect(),
// player_time_ms: todo!(),
video_playback_ustreamer_config: Some(self.ustreamer_config.clone()),
selected_audio_format_ids: audio_format_ids,
selected_video_format_ids: video_format_ids,
streamer_context: MessageField::some(StreamerContext {
client_info: MessageField::some(ClientInfo {
client_name: Some(1),
client_version: Some("2.20250314.01.00".to_owned()),
..Default::default()
}),
po_token: self.po_token.clone(),
playback_cookie: state
.playback_cookie
.as_ref()
.and_then(|c| c.write_to_bytes().ok()),
..Default::default()
}),
..Default::default()
};
let res = self
.http
.post(&state.abr_streaming_url)
.body(body.write_to_bytes()?)
.send()
.await?
.error_for_status()?;
// let dstream = res.bytes_stream();
let data = res.bytes().await?;
tracing::debug!("got {} bytes", data.len());
Ok(data)
}
fn parse_ump(data: &[u8]) -> Vec<UmpPart<'_>> {
let mut parts = Vec::new();
let mut offset = 0;
while offset < data.len() {
let (part_type, new_offset) = Self::read_varint(&data[offset..]);
if new_offset == 0 {
break;
}
offset += new_offset;
let (part_size, new_offset) = Self::read_varint(&data[offset..]);
if new_offset == 0 {
break;
}
offset += new_offset;
let data_offset = offset + part_size as usize;
let part_data = &data[offset..data_offset.min(data.len())];
offset = data_offset;
parts.push(UmpPart {
part_type: part_type.into(),
size: part_size,
data: part_data,
});
}
parts
}
fn read_varint(data: &[u8]) -> (u64, usize) {
/*
fn varint_size(byte: u8) -> u8 {
let mut lo = 0;
for i in (4u8..8).rev() {
if byte & (1 << i) == 0 {
break;
} else {
lo += 1;
}
}
return (lo + 1).min(5);
}
if data.is_empty() {
return (0, 0);
}
let prefix = data[0];
let size = varint_size(prefix);
let mut result = 0u64;
let mut shift = 0;
if size != 5 {
shift = 8 - size;
let mask = (1 << shift) - 1;
result |= u64::from(prefix) & mask;
}
let total_size = usize::from(size) + 1;
for byte in &data[1..total_size] {
result |= u64::from(*byte) << shift;
shift += 8;
}
(result, total_size)
*/
// Determine the length of the val
if data.is_empty() {
return (0, 0);
}
let fb = data[0];
let byte_length = if fb < 128 {
1
} else if fb < 192 {
2
} else if fb < 224 {
3
} else if fb < 240 {
4
} else {
5
};
if data.len() < byte_length {
return (0, 0);
}
let n = match byte_length {
1 => data[0].into(),
2 => {
let b1 = u64::from(data[0]);
let b2 = u64::from(data[1]);
(b1 & 0x3f) + 64 * b2
}
3 => {
let b1 = u64::from(data[0]);
let b2 = u64::from(data[1]);
let b3 = u64::from(data[2]);
(b1 & 0x1f) + 32 * (b2 + 256 * b3)
}
4 => {
let b1 = u64::from(data[0]);
let b2 = u64::from(data[1]);
let b3 = u64::from(data[2]);
let b4 = u64::from(data[3]);
(b1 & 0x0f) + 16 * (b2 + 256 * (b3 + 256 * b4))
}
_ => u32::from_be_bytes([data[1], data[2], data[3], data[4]]).into(),
};
(n, byte_length)
}
}
impl StreamingState {
fn new(url: String, abr_state: ClientAbrState) -> Self {
Self {
abr_streaming_url: url,
abr_state,
playback_cookie: None,
backoff_time_ms: 0,
formats_by_key: HashMap::new(),
header_id_to_format_key_map: HashMap::new(),
previous_sequences: HashMap::new(),
}
}
fn process_ump_parts(&mut self, ump_parts: &[UmpPart]) -> Result<bool, AbrError> {
for f in self.formats_by_key.values_mut() {
f.sequence_list.clear();
}
for part in ump_parts {
match part.part_type {
PartType::MEDIA_HEADER => {
self.process_media_header(part.data)?;
}
PartType::MEDIA => {
self.process_media_data(part.data)?;
}
PartType::MEDIA_END => {
self.process_media_end(part.data);
}
PartType::NEXT_REQUEST_POLICY => {
self.process_next_request_policy(part.data)?;
}
PartType::FORMAT_INITIALIZATION_METADATA => {
self.process_format_initialization(part.data)?;
}
PartType::SABR_ERROR => {
return Err(AbrError::Sabr(SabrError::parse_from_bytes(part.data)?));
}
PartType::SABR_REDIRECT => {
self.process_sabr_redirect(part.data)?;
}
PartType::STREAM_PROTECTION_STATUS => {
let prot = StreamProtectionStatus::parse_from_bytes(part.data)?;
match prot.status() {
1 => tracing::debug!("[StreamProtectionStatus]: Good"),
2 => tracing::warn!("[StreamProtectionStatus]: Attestation pending"),
3 => return Err(AbrError::Attestation),
_ => {}
}
}
_ => {}
}
}
let main_format = self
.formats_by_key
.values()
.find(|fmt| {
fmt.mime_type
.as_deref()
.map(|t| t.contains("video"))
.unwrap_or_default()
})
.or(self.formats_by_key.values().next());
if let Some(main_format) = main_format {
self.abr_state.set_player_time_ms(
self.abr_state.player_time_ms()
+ main_format.sequence_list.iter().fold(0, |acc, seq| {
acc + i64::from(seq.duration_ms.unwrap_or_default())
}),
);
tracing::debug!(
"sequence_count={}, last={}",
main_format.sequence_count.unwrap_or_default(),
main_format
.sequence_list
.last()
.and_then(|x| x.sequence_number)
.unwrap_or_default()
);
let is_last = main_format
.sequence_count
.zip(main_format.sequence_list.last())
.map(|(sc, last)| last.sequence_number == Some(sc.into()))
.unwrap_or_default();
Ok(is_last)
} else {
Ok(true)
}
}
fn process_media_header(&mut self, data: &[u8]) -> Result<(), AbrError> {
let mh = MediaHeader::parse_from_bytes(data)?;
let format_id = mh
.format_id
.as_ref()
.ok_or(AbrError::Invalid("media header: no format_id".into()))?;
let header_id = mh
.header_id
.and_then(|hid| u8::try_from(hid).ok())
.ok_or(AbrError::Invalid("media header: no header_id".into()))?;
let format_key = get_format_key(format_id);
if !self.formats_by_key.contains_key(&format_key) {
self.formats_by_key
.insert(format_key.to_owned(), InitializedFormat::try_from(&mh)?);
}
let current_format = self.formats_by_key.get_mut(&format_key).unwrap();
// This is a hacky workaround to prevent duplicate sequences from being added. This should be fixed in the future
// (preferably by figuring out how to make the server not send duplicates).
if let Some(sequence_number) = mh.sequence_number {
let pseq = self
.previous_sequences
.entry(format_key.to_owned())
.or_default();
if !pseq.insert(sequence_number) {
tracing::warn!("duplicate sequence {sequence_number}");
return Ok(());
}
}
// Save the header's ID so we can identify its stream data later.
self.header_id_to_format_key_map
.entry(header_id)
.or_insert(format_key);
if current_format
.sequence_list
.iter()
.all(|x| x.sequence_number != Some(mh.sequence_number()))
{
current_format.sequence_list.push(Sequence {
itag: mh.itag,
format_id: format_id.clone(),
is_init_segment: mh.is_init_seg(),
duration_ms: mh.duration_ms,
start_ms: mh.start_ms,
start_data_range: mh.start_data_range,
sequence_number: mh.sequence_number,
content_length: mh.content_length,
time_range: mh.time_range.as_ref().cloned(),
});
if mh.has_sequence_number() {
current_format.state.set_duration_ms(
current_format.state.duration_ms() + i64::from(mh.duration_ms()),
);
current_format
.state
.set_end_segment_index(current_format.state.end_segment_index() + 1);
}
}
Ok(())
}
fn process_media_data(&mut self, data: &[u8]) -> Result<(), AbrError> {
let header_id = data[0];
let format_key = self
.header_id_to_format_key_map
.get(&header_id)
.ok_or_else(|| {
AbrError::Invalid(format!("media: unknown header id {header_id}").into())
})?;
let current_format = self
.formats_by_key
.get_mut(format_key)
.ok_or(AbrError::Invalid("no current format".into()))?;
current_format.media_chunks.extend_from_slice(&data[1..]);
Ok(())
}
fn process_media_end(&mut self, data: &[u8]) {
let header_id = data[0];
self.header_id_to_format_key_map.remove(&header_id);
}
fn process_next_request_policy(&mut self, data: &[u8]) -> Result<(), AbrError> {
let mut policy = NextRequestPolicy::parse_from_bytes(data)?;
self.playback_cookie = policy.playback_cookie.take();
self.backoff_time_ms = policy.backoff_time_ms();
Ok(())
}
fn process_format_initialization(&mut self, data: &[u8]) -> Result<(), AbrError> {
let init = FormatInitializationMetadata::parse_from_bytes(data)?;
let format = InitializedFormat::try_from(init)?;
let format_key = get_format_key(&format.format_id);
self.formats_by_key.insert(format_key, format);
Ok(())
}
fn process_sabr_redirect(&mut self, data: &[u8]) -> Result<(), AbrError> {
let redir = SabrRedirect::parse_from_bytes(data)?;
self.abr_streaming_url = redir
.url
.ok_or(AbrError::Invalid("sabr redirect: no URL".into()))?;
Ok(())
}
}
fn get_format_key(format_id: &FormatId) -> String {
format!("{};{};", format_id.itag(), format_id.last_modified())
}
impl TryFrom<&MediaHeader> for InitializedFormat {
type Error = AbrError;
fn try_from(value: &MediaHeader) -> Result<Self, Self::Error> {
let format_id = value
.format_id
.as_ref()
.ok_or(AbrError::Invalid("media header: no format_id".into()))?;
Ok(InitializedFormat {
format_id: format_id.clone(),
duration_ms: value.duration_ms,
mime_type: None,
sequence_count: None,
sequence_list: Vec::new(),
media_chunks: Vec::new(),
state: BufferedRange {
format_id: MessageField::some(format_id.clone()),
start_time_ms: Some(0),
duration_ms: Some(0),
start_segment_index: Some(1),
end_segment_index: Some(0),
..Default::default()
},
})
}
}
impl TryFrom<FormatInitializationMetadata> for InitializedFormat {
type Error = AbrError;
fn try_from(value: FormatInitializationMetadata) -> Result<Self, Self::Error> {
let format_id = *value.format_id.0.ok_or(AbrError::Invalid(
"format initialization: no format_id".into(),
))?;
Ok(InitializedFormat {
format_id: format_id.clone(),
duration_ms: value.duration_ms,
mime_type: value.mime_type.clone(),
sequence_count: value.sequence_count,
sequence_list: Vec::new(),
media_chunks: Vec::new(),
state: BufferedRange {
format_id: MessageField::some(format_id),
start_time_ms: Some(0),
duration_ms: Some(0),
start_segment_index: Some(1),
end_segment_index: Some(0),
..Default::default()
},
})
}
}
#[cfg(test)]
mod tests {
use rustypipe::{
client::RustyPipe,
model::{AudioCodec, VideoFormat},
param::StreamFilter,
};
use crate::abr::{AbrStream, StreamingState};
#[tokio::test]
#[tracing_test::traced_test]
async fn experiment() {
let rp = RustyPipe::new();
let player = rp.query().player("UMm7Pq0BdRg").await.unwrap();
let stream_filter = StreamFilter::new()
.allow_abr_only()
.video_max_res(360)
.video_formats([VideoFormat::Webm])
.audio_codecs([AudioCodec::Opus]);
let (video, audio) = player.select_video_audio_stream(&stream_filter);
let _video = video.unwrap();
let audio = audio.unwrap();
let http = reqwest::Client::new();
let abr = AbrStream::new(
player.abr_streaming_url.clone().unwrap(),
player.abr_ustreamer_config.as_deref().unwrap(),
player.po_token.as_deref(),
http,
)
.unwrap();
let state = abr.init(&[audio], &[], Default::default()).await.unwrap();
dbg!(&state);
// let f = state.formats_by_key.values().next().unwrap();
// std::fs::write("test.webm", &f.media_chunks);
}
#[test]
#[tracing_test::traced_test]
fn parse_ump() {
let data = std::fs::read("abr.bin").unwrap();
let ump_parts = AbrStream::parse_ump(&data);
dbg!(&ump_parts);
let mut state = StreamingState::new(Default::default(), Default::default());
assert!(!state.process_ump_parts(&ump_parts).unwrap());
dbg!(&state);
}
}

931
downloader/src/abr2.rs Normal file
View file

@ -0,0 +1,931 @@
#![allow(missing_docs, unused)]
use std::collections::{HashMap, HashSet};
use async_stream::try_stream;
use bytes::{Buf, Bytes};
use data_encoding::BASE64URL;
use futures_util::{Stream, StreamExt, TryStreamExt};
use protobuf::{Message, MessageField};
use reqwest::Client;
use rustypipe::model::{AudioStream, VideoStream};
use rustypipe_abr_proto::{
buffered_range::BufferedRange,
client_abr_state::ClientAbrState,
common::FormatId,
format_initialization_metadata::FormatInitializationMetadata,
media_header::MediaHeader,
next_request_policy::NextRequestPolicy,
playback_cookie::PlaybackCookie,
reload_player_response::ReloadPlayerResponse,
sabr_context_sending_policy::SabrContextSendingPolicy,
sabr_context_update::{sabr_context_update::SabrContextWritePolicy, SabrContextUpdate},
sabr_error::SabrError,
sabr_redirect::SabrRedirect,
sabr_seek::SabrSeek,
stream_protection_status::StreamProtectionStatus,
streamer_context::{
streamer_context::{ClientInfo, SabrContext},
StreamerContext,
},
video_playback_abr_request::VideoPlaybackAbrRequest,
};
use serde::{Deserialize, Serialize};
use crate::{abr_model::PartType, error::AbrError, util::BytesBuffer};
pub struct AbrStream {
pub http: Client,
pub url: String,
pub ustreamer_config: Vec<u8>,
pub po_token: Option<Vec<u8>>,
pub abr_state: ClientAbrState,
pub audio_format_ids: Vec<FormatId>,
pub video_format_ids: Vec<FormatId>,
pub playback_cookie: Option<PlaybackCookie>,
pub backoff_time_ms: i32,
pub formats_by_key: HashMap<String, InitializedFormat>,
pub header_id_to_format_key_map: HashMap<u8, String>,
pub previous_sequences: HashMap<String, HashSet<i64>>,
pub buffer: BytesBuffer,
pub last_header: Option<UmpHeader>,
pub sabr_context_updates: HashMap<i32, SabrContextUpdate>,
pub sabr_contexts_to_send: HashSet<i32>,
}
#[derive(Default, Clone)]
pub struct AbrStreamOptions<'a> {
pub http: Client,
pub url: String,
pub ustreamer_config: &'a str,
pub po_token: Option<&'a str>,
pub audio_streams: &'a [&'a AudioStream],
pub video_streams: &'a [&'a VideoStream],
pub player_time: i64,
pub abr_state: ClientAbrState,
}
#[derive(Clone)]
pub struct AbrStreamItem {
pub format: FormatInfo,
pub offset: i32,
pub seq: i64,
pub start_ms: i32,
pub data: Bytes,
}
#[derive(Debug, Clone)]
pub struct FormatInfo {
pub itag: i32,
pub last_modified: u64,
pub xtags: Option<String>,
pub mime_type: Option<String>,
}
#[derive(Debug)]
pub struct InitializedFormat {
pub format_id: FormatId,
pub duration_ms: Option<i32>,
pub mime_type: Option<String>,
pub sequence_count: Option<i32>,
pub sequence_list: Vec<Sequence>,
pub state: BufferedRange,
}
#[derive(Debug, Clone, Serialize)]
pub struct Sequence {
pub itag: Option<i32>,
#[serde(skip)]
pub format_id: FormatId,
pub is_init_segment: bool,
pub duration_ms: Option<i32>,
pub start_ms: Option<i32>,
pub start_data_range: Option<i32>,
pub sequence_number: Option<i64>,
pub content_length: Option<i64>,
pub time_range: Option<TimeRange>,
#[serde(skip)]
pub bytes_written: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimeRange {
pub start: i64,
pub duration: i64,
pub timescale: i32,
}
#[derive(Debug, Clone)]
pub struct UmpHeader {
part_type: PartType,
size: u64,
header_id: Option<u8>,
}
impl std::fmt::Debug for AbrStreamItem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"AbrStreamItem {{#{} seq{} {}ms {}-{}}}",
self.format.itag,
self.seq,
self.start_ms,
self.offset,
self.offset as usize + self.data.len()
)
}
}
impl From<&InitializedFormat> for FormatInfo {
fn from(value: &InitializedFormat) -> Self {
Self {
itag: value.format_id.itag(),
last_modified: value.format_id.last_modified(),
xtags: value.format_id.xtags.clone(),
mime_type: value.mime_type.clone(),
}
}
}
impl AbrStream {
pub fn new(options: AbrStreamOptions) -> Result<Self, AbrError> {
let ustreamer_config = BASE64URL
.decode(options.ustreamer_config.as_bytes())
.map_err(|e| {
AbrError::Invalid(format!("could not parse ustreamer_config: {e}").into())
})?;
let po_token =
match options.po_token {
Some(pot) => Some(BASE64URL.decode(pot.as_bytes()).map_err(|e| {
AbrError::Invalid(format!("could not parse po_token: {e}").into())
})?),
None => None,
};
let mut abr_state = options.abr_state;
if !abr_state.has_last_manual_direction() {
abr_state.set_last_manual_direction(0);
}
if !abr_state.has_time_since_last_manual_format_selection_ms() {
abr_state.set_time_since_last_manual_format_selection_ms(0);
}
if !abr_state.has_player_time_ms() {
abr_state.set_player_time_ms(options.player_time);
}
if !abr_state.has_visibility() {
abr_state.set_visibility(0);
}
if !abr_state.has_enabled_track_types_bitfield() {
abr_state.set_enabled_track_types_bitfield(i32::from(options.video_streams.is_empty()));
}
if !abr_state.has_audio_track_id() {
if let Some(audio_track) = options.audio_streams.first().and_then(|s| s.track.as_ref())
{
abr_state.set_audio_track_id(audio_track.id.to_owned());
}
}
if let Some(v) = options.video_streams.first() {
if !abr_state.has_last_manual_selected_resolution() {
abr_state.set_last_manual_selected_resolution(v.height as i32);
}
if !abr_state.has_sticky_resolution() {
abr_state.set_sticky_resolution(v.height as i32);
}
}
if !abr_state.has_drc_enabled() {
if let Some(audio) = options.audio_streams.first() {
abr_state.set_drc_enabled(audio.is_drc);
}
}
let audio_format_ids = options
.audio_streams
.iter()
.map(|s| FormatId {
itag: Some(s.itag as i32),
last_modified: s.last_modified,
xtags: s.xtags.clone(),
..Default::default()
})
.collect::<Vec<_>>();
let video_format_ids = options
.video_streams
.iter()
.map(|s| FormatId {
itag: Some(s.itag as i32),
last_modified: s.last_modified,
xtags: s.xtags.clone(),
..Default::default()
})
.collect::<Vec<_>>();
Ok(Self {
http: options.http,
url: options.url,
ustreamer_config,
po_token,
abr_state,
audio_format_ids,
video_format_ids,
playback_cookie: None,
backoff_time_ms: 0,
formats_by_key: HashMap::new(),
header_id_to_format_key_map: HashMap::new(),
previous_sequences: HashMap::new(),
buffer: BytesBuffer::new(),
last_header: None,
sabr_context_updates: HashMap::new(),
sabr_contexts_to_send: HashSet::new(),
})
}
fn read_varint(&mut self, offset: usize) -> (u64, usize) {
// Determine the length of the val
let buffer_len = self.buffer.remaining().saturating_sub(offset);
if buffer_len == 0 {
return (0, 0);
}
let fb = self.buffer[offset];
let byte_length = if fb < 128 {
1
} else if fb < 192 {
2
} else if fb < 224 {
3
} else if fb < 240 {
4
} else {
5
};
if buffer_len < byte_length {
return (0, 0);
}
let n = match byte_length {
1 => self.buffer[offset].into(),
2 => {
let b1 = u64::from(self.buffer[offset]);
let b2 = u64::from(self.buffer[offset + 1]);
(b1 & 0x3f) + 64 * b2
}
3 => {
let b1 = u64::from(self.buffer[offset]);
let b2 = u64::from(self.buffer[offset + 1]);
let b3 = u64::from(self.buffer[offset + 2]);
(b1 & 0x1f) + 32 * (b2 + 256 * b3)
}
4 => {
let b1 = u64::from(self.buffer[offset]);
let b2 = u64::from(self.buffer[offset + 1]);
let b3 = u64::from(self.buffer[offset + 2]);
let b4 = u64::from(self.buffer[offset + 3]);
(b1 & 0x0f) + 16 * (b2 + 256 * (b3 + 256 * b4))
}
_ => u32::from_be_bytes([
self.buffer[offset + 1],
self.buffer[offset + 2],
self.buffer[offset + 3],
self.buffer[offset + 4],
])
.into(),
};
(n, offset + byte_length)
}
pub fn parse_ump_header(&mut self) -> Option<UmpHeader> {
let (part_type, o1) = self.read_varint(0);
if o1 == 0 {
return None;
}
let (part_size, o2) = self.read_varint(o1);
if o2 == 0 {
return None;
}
self.buffer.advance(o2);
Some(UmpHeader {
part_type: part_type.into(),
size: part_size,
header_id: None,
})
}
fn clear_sequence_list(&mut self) {
for f in self.formats_by_key.values_mut() {
f.sequence_list.clear();
}
}
pub fn process_ump_part(&mut self) -> Result<(Option<AbrStreamItem>, bool), AbrError> {
if let Some(last_header) = self.last_header.clone() {
let hsize = last_header.size as usize;
if last_header.part_type == PartType::MEDIA {
// Media data may be processed if the part is only partially downloaded
self.process_media_data().map(|x| {
let eod = x.is_none();
(x, eod)
})
} else if self.buffer.remaining() < hsize {
// Wait for the entire part to be downloaded
tracing::info!("waiting for entire part");
Ok((None, true))
} else {
let part_data = self.buffer.copy_to_bytes(hsize);
match last_header.part_type {
PartType::MEDIA_HEADER => self.process_media_header(&part_data)?,
PartType::MEDIA_END => self.process_media_end(&part_data),
PartType::NEXT_REQUEST_POLICY => {
self.process_next_request_policy(&part_data)?
}
PartType::RELOAD_PLAYER_RESPONSE => self.process_reload_player(&part_data)?,
PartType::FORMAT_INITIALIZATION_METADATA => {
self.process_format_initialization(&part_data)?
}
PartType::SABR_ERROR => {
return Err(AbrError::Sabr(SabrError::parse_from_bytes(&part_data)?));
}
PartType::SABR_REDIRECT => self.process_sabr_redirect(&part_data)?,
PartType::STREAM_PROTECTION_STATUS => {
let prot = StreamProtectionStatus::parse_from_bytes(&part_data)?;
match prot.status() {
1 => tracing::debug!("[StreamProtectionStatus]: Good"),
2 => tracing::warn!("[StreamProtectionStatus]: Attestation pending"),
3 => return Err(AbrError::Attestation),
_ => {}
}
}
PartType::SABR_SEEK => self.process_sabr_seek(&part_data)?,
PartType::SABR_CONTEXT_UPDATE => {
self.process_sabr_context_update(&part_data)?
}
PartType::SABR_CONTEXT_SENDING_POLICY => {
self.process_sabr_context_sending_policy(&part_data)?
}
_ => {}
};
self.last_header = None;
Ok((None, false))
}
} else {
Ok((None, true))
}
}
fn process_media_header(&mut self, data: &[u8]) -> Result<(), AbrError> {
let mh = MediaHeader::parse_from_bytes(data)?;
let format_id = mh
.format_id
.as_ref()
.ok_or(AbrError::Invalid("media header: no format_id".into()))?;
let header_id = mh
.header_id
.and_then(|hid| u8::try_from(hid).ok())
.ok_or(AbrError::Invalid("media header: no header_id".into()))?;
let format_key = get_format_key(format_id);
if !self.formats_by_key.contains_key(&format_key) {
self.formats_by_key
.insert(format_key.to_owned(), InitializedFormat::try_from(&mh)?);
}
let current_format = self.formats_by_key.get_mut(&format_key).unwrap();
// This is a hacky workaround to prevent duplicate sequences from being added. This should be fixed in the future
// (preferably by figuring out how to make the server not send duplicates).
if let Some(sequence_number) = mh.sequence_number {
let pseq = self
.previous_sequences
.entry(format_key.to_owned())
.or_default();
if !pseq.insert(sequence_number) {
tracing::warn!("duplicate sequence {sequence_number}");
return Ok(());
}
}
// Save the header's ID so we can identify its stream data later.
self.header_id_to_format_key_map
.entry(header_id)
.or_insert(format_key);
if current_format
.sequence_list
.iter()
.all(|x| x.sequence_number != Some(mh.sequence_number()))
{
let tr = mh.time_range.as_ref();
let seq = Sequence {
itag: mh.itag,
format_id: format_id.clone(),
is_init_segment: mh.is_init_seg(),
duration_ms: mh.duration_ms,
start_ms: mh.start_ms,
start_data_range: mh.start_data_range,
sequence_number: mh.sequence_number,
content_length: mh.content_length,
time_range: tr.and_then(|tr| {
tr.start.zip(tr.duration).zip(tr.timescale).map(
|((start, duration), timescale)| TimeRange {
start,
duration,
timescale,
},
)
}),
bytes_written: 0,
};
// TODO: testing
let sname = if mh.itag == Some(251) {
"audio"
} else {
"video"
};
tracing::debug!(
"{sname} #{}: content_length={} start_data_range={} time_start={} time_duration={}",
seq.sequence_number.unwrap_or_default(),
seq.content_length.unwrap_or_default(),
seq.start_data_range.unwrap_or_default(),
seq.time_range.as_ref().map(|t| t.start).unwrap_or_default(),
seq.time_range
.as_ref()
.map(|t| t.duration)
.unwrap_or_default(),
);
current_format.sequence_list.push(seq);
if let Some(sequence_number) = mh.sequence_number {
current_format
.state
.set_duration_ms(i64::from(mh.start_ms()) + i64::from(mh.duration_ms()));
current_format
.state
.set_end_segment_index(sequence_number as i32);
}
}
Ok(())
}
pub fn process_media_data(&mut self) -> Result<Option<AbrStreamItem>, AbrError> {
if let Some(last_header) = &mut self.last_header {
if let Some(header_id) = last_header.header_id.or_else(|| {
let x = self.buffer.try_get_u8().ok();
if x.is_some() {
last_header.header_id = x;
last_header.size -= 1;
}
x
}) {
let to_copy = self.buffer.remaining().min(last_header.size as usize);
if to_copy == 0 {
return Ok(None);
}
let format_key = self
.header_id_to_format_key_map
.get(&header_id)
.ok_or_else(|| {
AbrError::Invalid(format!("media: unknown header id {header_id}").into())
})?;
let current_format = self
.formats_by_key
.get(format_key)
.ok_or(AbrError::Invalid("no current format".into()))?;
let format = FormatInfo::from(current_format);
let data = self.buffer.copy_to_bytes(to_copy);
last_header.size -= data.len() as u64;
if last_header.size == 0 {
self.last_header = None;
}
let seq = self
.formats_by_key
.get_mut(format_key)
.unwrap()
.sequence_list
.last_mut()
.ok_or(AbrError::Invalid("no current sequence".into()))?;
let offset = seq.start_data_range.unwrap_or_default() + seq.bytes_written;
seq.bytes_written += data.len() as i32;
Ok(Some(AbrStreamItem {
format,
offset,
seq: seq.sequence_number.unwrap_or_default(),
start_ms: seq.start_ms.unwrap_or_default(),
data,
}))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
fn process_media_end(&mut self, data: &[u8]) {
let header_id = data[0];
self.header_id_to_format_key_map.remove(&header_id);
}
fn process_next_request_policy(&mut self, data: &[u8]) -> Result<(), AbrError> {
let mut policy = NextRequestPolicy::parse_from_bytes(data)?;
tracing::debug!("NextRequestPolicy {policy:?}");
self.playback_cookie = policy.playback_cookie.take();
self.backoff_time_ms = policy.backoff_time_ms();
Ok(())
}
fn process_reload_player(&mut self, data: &[u8]) -> Result<(), AbrError> {
let mut reload = ReloadPlayerResponse::parse_from_bytes(data)?;
tracing::debug!("ReloadPlayerResponse {reload:?}");
Ok(())
}
fn process_format_initialization(&mut self, data: &[u8]) -> Result<(), AbrError> {
let init = FormatInitializationMetadata::parse_from_bytes(data)?;
let format = InitializedFormat::try_from(init)?;
let format_key = get_format_key(&format.format_id);
self.formats_by_key.insert(format_key, format);
Ok(())
}
fn process_sabr_redirect(&mut self, data: &[u8]) -> Result<(), AbrError> {
let redir = SabrRedirect::parse_from_bytes(data)?;
self.url = redir
.url
.ok_or(AbrError::Invalid("sabr redirect: no URL".into()))?;
Ok(())
}
fn process_sabr_seek(&mut self, data: &[u8]) -> Result<(), AbrError> {
let seek = SabrSeek::parse_from_bytes(data)?;
if let Some((seek_time_ticks, timescale)) = seek.seek_time_ticks.zip(seek.timescale) {
let seek_to = i64::from(seek_time_ticks) * 1000 / i64::from(timescale);
tracing::debug!("Seeking to {seek_to}ms");
self.abr_state.set_player_time_ms(seek_to);
/*
TODO: Clear latest segment of each initialized format
as we expect them to no longer be in order.
*/
}
Ok(())
}
fn process_sabr_context_update(&mut self, data: &[u8]) -> Result<(), AbrError> {
let sabr_ctx_update = SabrContextUpdate::parse_from_bytes(data)?;
if let Some((typ, value)) = sabr_ctx_update
.type_
.as_ref()
.zip(sabr_ctx_update.value.as_ref())
{
if sabr_ctx_update.write_policy()
== SabrContextWritePolicy::SABR_CONTEXT_WRITE_POLICY_KEEP_EXISTING
&& self.sabr_context_updates.contains_key(typ)
{
tracing::debug!("Received a SABR Context Update with write_policy=KEEP_EXISTING matching an existing SABR Context Update. Ignoring update")
} else {
tracing::warn!("Received a SABR Context Update. YouTube is likely trying to force ads on the client.");
tracing::debug!("Registered SabrContextUpdate {sabr_ctx_update:?}");
if sabr_ctx_update.send_by_default() {
self.sabr_contexts_to_send.insert(*typ);
}
self.sabr_context_updates.insert(*typ, sabr_ctx_update);
}
} else {
tracing::warn!("Received an invalid SabrContextUpdate, ignoring")
}
Ok(())
}
fn process_sabr_context_sending_policy(&mut self, data: &[u8]) -> Result<(), AbrError> {
let policy = SabrContextSendingPolicy::parse_from_bytes(data)?;
for start_type in policy.start_policy {
if !self.sabr_context_updates.contains_key(&start_type) {
tracing::debug!(
"Server requested to enable SABR Context Update for type {start_type}"
);
self.sabr_contexts_to_send.insert(start_type);
}
}
for stop_type in policy.stop_policy {
if self.sabr_context_updates.contains_key(&stop_type) {
tracing::debug!(
"Server requested to disable SABR Context Update for type {stop_type}"
);
self.sabr_contexts_to_send.remove(&stop_type);
}
}
for discard_type in policy.discard_policy {
if self.sabr_context_updates.contains_key(&discard_type) {
tracing::debug!(
"Server requested to discard SABR Context Update for type {discard_type}"
);
self.sabr_context_updates.remove(&discard_type);
}
}
Ok(())
}
fn main_format(&self) -> Option<&InitializedFormat> {
self.formats_by_key
.values()
.find(|fmt| {
fmt.mime_type
.as_deref()
.map(|t| t.contains("video"))
.unwrap_or_default()
})
.or(self.formats_by_key.values().next())
}
/// A stream is done if all sequences from all formats were received or the last request did not contain any sequences
fn is_done(&self) -> bool {
self.formats_by_key.values().all(|f| {
f.sequence_list.is_empty()
|| f.sequence_count
.zip(f.sequence_list.last())
.map(|(sc, last)| last.sequence_number == Some(sc.into()))
.unwrap_or_default()
})
}
pub async fn fetch_stream_data(&self) -> Result<reqwest::Response, AbrError> {
tracing::debug!(
"fetching stream data: {}ms",
self.abr_state.player_time_ms()
);
let body = VideoPlaybackAbrRequest {
client_abr_state: MessageField::some(self.abr_state.clone()),
selected_format_ids: self
.formats_by_key
.values()
.map(|f| f.format_id.clone())
.collect(),
buffered_ranges: self
.formats_by_key
.values()
.map(|f| f.state.clone())
.collect(),
video_playback_ustreamer_config: Some(self.ustreamer_config.clone()),
selected_audio_format_ids: self.audio_format_ids.clone(),
selected_video_format_ids: self.video_format_ids.clone(),
streamer_context: MessageField::some(StreamerContext {
client_info: MessageField::some(ClientInfo {
client_name: Some(1),
client_version: Some("2.20250314.01.00".to_owned()),
..Default::default()
}),
po_token: self.po_token.clone(),
playback_cookie: self
.playback_cookie
.as_ref()
.and_then(|c| c.write_to_bytes().ok()),
sabr_contexts: self
.sabr_context_updates
.values()
.filter(|v| self.sabr_contexts_to_send.contains(&v.type_()))
.map(|v| SabrContext {
type_: v.type_,
value: v.value.clone(),
..Default::default()
})
.collect(),
unsent_sabr_contexts: self
.sabr_contexts_to_send
.iter()
.copied()
.filter(|v| !self.sabr_context_updates.contains_key(v))
.collect(),
..Default::default()
}),
..Default::default()
};
let resp = self
.http
.post(&self.url)
.body(body.write_to_bytes()?)
.send()
.await?
.error_for_status()?;
Ok(resp)
}
pub fn stream(mut self) -> impl Stream<Item = Result<AbrStreamItem, AbrError>> {
let stream = try_stream! {
let mut ri = 1;
loop {
let resp = self.fetch_stream_data().await?;
let mut data_stream = resp.bytes_stream();
let mut page_len = 0;
self.clear_sequence_list();
while let Some(chunk) = data_stream.next().await {
let chunk = chunk?;
let clen = chunk.len();
page_len += clen;
tracing::debug!("chunk {clen}");
self.buffer.push(chunk);
loop {
if self.last_header.is_none() {
self.last_header = self.parse_ump_header();
}
tracing::debug!("loop {:?}", self.last_header);
if self.last_header.is_none() {
break;
}
let (x, y) = self.process_ump_part()?;
if let Some(item) = x {
yield item;
}
if y {
break;
}
}
}
tracing::debug!("request #{ri} fetched {page_len} bytes");
if let Some(main_format) = self.main_format() {
let player_time = self.abr_state.player_time_ms()
+ main_format.sequence_list.iter().fold(0, |acc, seq| {
acc + i64::from(seq.duration_ms.unwrap_or_default())
});
tracing::debug!(
"player time: {player_time}; sequence_count={}, last={:?}",
main_format.sequence_count.unwrap_or_default(),
main_format.sequence_list.last().and_then(|x| x.sequence_number),
);
if self.is_done() {
break;
}
self.abr_state.set_player_time_ms(player_time);
}
if self.backoff_time_ms > 0 {
tracing::debug!("backoff: {} ms", self.backoff_time_ms);
tokio::time::sleep(std::time::Duration::from_millis(
self.backoff_time_ms.try_into().unwrap_or_default(),
)).await;
self.backoff_time_ms = 0;
}
ri += 1;
}
// DEBUG:
// for f in self.formats_by_key.values() {
// let mut of = File::create(format!("/home/thetadev/Documents/Programmieren/Rust/rustypipe/tmp/abrmap/{}_{}.json", std::env::args().into_iter().nth(1).unwrap(), f.format_id.itag())).unwrap();
// serde_json::to_writer(of, &f.sequence_list2).unwrap();
// }
};
Box::pin(stream)
}
}
fn get_format_key(format_id: &FormatId) -> String {
format!("{};{};", format_id.itag(), format_id.last_modified())
}
impl TryFrom<&MediaHeader> for InitializedFormat {
type Error = AbrError;
fn try_from(value: &MediaHeader) -> Result<Self, Self::Error> {
let format_id = value
.format_id
.as_ref()
.ok_or(AbrError::Invalid("media header: no format_id".into()))?;
Ok(InitializedFormat {
format_id: format_id.clone(),
duration_ms: value.duration_ms,
mime_type: None,
sequence_count: None,
sequence_list: Vec::new(),
state: BufferedRange {
format_id: MessageField::some(format_id.clone()),
start_time_ms: Some(0),
duration_ms: Some(0),
start_segment_index: Some(1),
end_segment_index: Some(0),
..Default::default()
},
})
}
}
impl TryFrom<FormatInitializationMetadata> for InitializedFormat {
type Error = AbrError;
fn try_from(value: FormatInitializationMetadata) -> Result<Self, Self::Error> {
let format_id = *value.format_id.0.ok_or(AbrError::Invalid(
"format initialization: no format_id".into(),
))?;
Ok(InitializedFormat {
format_id: format_id.clone(),
duration_ms: value.duration_ms,
mime_type: value.mime_type.clone(),
sequence_count: value.sequence_count,
sequence_list: Vec::new(),
state: BufferedRange {
format_id: MessageField::some(format_id),
start_time_ms: Some(0),
duration_ms: Some(0),
start_segment_index: Some(1),
end_segment_index: Some(0),
..Default::default()
},
})
}
}
#[cfg(test)]
mod tests {
use std::io::Write;
use rustypipe::{
client::RustyPipe,
model::{AudioCodec, VideoFormat},
param::StreamFilter,
};
use super::*;
#[tokio::test]
#[tracing_test::traced_test]
async fn experiment() {
let rp = RustyPipe::builder().build().unwrap();
let player = rp.query().player("ZeerrnuLi5E").await.unwrap();
let stream_filter = StreamFilter::new()
.allow_abr_only()
.video_max_res(360)
.video_formats([VideoFormat::Webm])
.audio_codecs([AudioCodec::Opus]);
let (video, audio) = player.select_video_audio_stream(&stream_filter);
let video = video.unwrap();
let audio = audio.unwrap();
let mut abr = AbrStream::new(AbrStreamOptions {
url: player.abr_streaming_url.clone().unwrap(),
ustreamer_config: player.abr_ustreamer_config.as_deref().unwrap(),
po_token: player.po_token.as_deref(),
audio_streams: &[audio],
// video_streams: &[video],
video_streams: &[],
player_time: 0,
..Default::default()
})
.unwrap();
{
let mut stream = abr.stream();
let mut files = HashMap::new();
while let Some(item) = stream.try_next().await.unwrap() {
let mut f = files.entry(item.format.itag).or_insert_with(|| {
std::fs::File::create(format!("test_{}.webm", item.format.itag)).unwrap()
});
f.write_all(&item.data).unwrap();
}
}
let audio_md = std::fs::metadata(format!("test_{}.webm", audio.itag)).unwrap();
assert_eq!(audio_md.len(), audio.size);
// let video_md = std::fs::metadata(format!("test_{}.webm", video.itag)).unwrap();
// assert_eq!(video_md.len(), video.size.unwrap());
}
#[tokio::test]
#[tracing_test::traced_test]
async fn first_header() {
let rp = RustyPipe::builder().build().unwrap();
let player = rp.query().player("8AeOP8u89iE").await.unwrap();
let stream_filter = StreamFilter::new()
.allow_abr_only()
// .video_max_res(360)
.video_formats([VideoFormat::Webm])
.audio_codecs([AudioCodec::Opus]);
let (video, audio) = player.select_video_audio_stream(&stream_filter);
let video = video.unwrap();
let audio = audio.unwrap();
let mut abr = AbrStream::new(AbrStreamOptions {
url: player.abr_streaming_url.clone().unwrap(),
ustreamer_config: player.abr_ustreamer_config.as_deref().unwrap(),
po_token: player.po_token.as_deref(),
audio_streams: &[audio],
video_streams: &[video],
// video_streams: &[],
..Default::default()
})
.unwrap();
todo!()
}
}

968
downloader/src/abr3.rs Normal file
View file

@ -0,0 +1,968 @@
#![allow(missing_docs)]
use std::{
collections::{HashMap, HashSet},
sync::{Arc, RwLock},
time::Instant,
};
use async_stream::try_stream;
use bytes::{Buf, Bytes};
use data_encoding::BASE64URL;
use futures_util::{Stream, StreamExt};
use protobuf::{Message, MessageField};
use reqwest::Client;
use tokio::sync::broadcast;
use rustypipe::model::{AudioStream, VideoStream};
use rustypipe_abr_proto::{
buffered_range::BufferedRange,
client_abr_state::ClientAbrState,
common::FormatId,
format_initialization_metadata::FormatInitializationMetadata,
media_header::MediaHeader,
next_request_policy::NextRequestPolicy,
playback_cookie::PlaybackCookie,
reload_player_response::ReloadPlayerResponse,
sabr_error::SabrError,
sabr_redirect::SabrRedirect,
stream_protection_status::StreamProtectionStatus,
streamer_context::{streamer_context::ClientInfo, StreamerContext},
time_range::TimeRange,
video_playback_abr_request::VideoPlaybackAbrRequest,
};
use crate::{abr_model::PartType, error::AbrError, util::BytesBuffer};
#[derive(Clone)]
pub struct AbrStream {
inner: Arc<AbrStreamInner>,
}
struct AbrStreamInner {
http: Client,
ustreamer_config: Vec<u8>,
cancel_tx: broadcast::Sender<()>,
vars: RwLock<AbrStreamVars>,
}
// Mutable data shared with multiple streamers
struct AbrStreamVars {
url: String,
po_token: Option<Vec<u8>>,
audio_format_ids: Vec<FormatId>,
video_format_ids: Vec<FormatId>,
abr_state: ClientAbrState,
last_seek: Instant,
last_format_selection: Instant,
formats_by_key: HashMap<String, InitializedFormat>,
playback_cookie: Option<PlaybackCookie>,
}
// Data associated with a single stream (owned by the streamer)
pub struct AbrStreamConsumer {
stream: AbrStream,
header_id_to_format_key_map: HashMap<u8, String>,
sequence_lists: HashMap<String, Vec<Sequence>>,
previous_sequences: HashMap<String, HashSet<i64>>,
backoff_time_ms: i32,
buffer: BytesBuffer,
last_header: Option<UmpHeader>,
}
#[derive(Default, Clone)]
pub struct AbrStreamOptions<'a> {
pub http: Client,
pub url: String,
pub ustreamer_config: &'a str,
pub po_token: Option<&'a str>,
pub audio_streams: &'a [&'a AudioStream],
pub video_streams: &'a [&'a VideoStream],
pub player_time_ms: i64,
pub abr_state: ClientAbrState,
}
#[derive(Clone)]
pub struct AbrStreamItem {
pub format: FormatInfo,
pub offset: i32,
pub seq: i64,
pub start_ms: i32,
pub data: Bytes,
}
#[derive(Debug, Clone)]
pub struct FormatInfo {
pub itag: i32,
pub last_modified: u64,
pub xtags: Option<String>,
}
#[derive(Debug, Clone)]
struct InitializedFormat {
format_id: FormatId,
duration_ms: Option<i32>,
mime_type: Option<String>,
sequence_count: Option<i32>,
state: BufferedRange,
}
#[derive(Debug, Clone)]
struct Sequence {
itag: Option<i32>,
format_id: FormatId,
is_init_segment: bool,
duration_ms: Option<i32>,
start_ms: Option<i32>,
start_data_range: Option<i32>,
sequence_number: Option<i64>,
content_length: Option<i64>,
time_range: Option<TimeRange>,
bytes_written: i32,
}
#[derive(Debug, Clone)]
struct UmpHeader {
part_type: PartType,
size: u64,
header_id: Option<u8>,
}
impl std::fmt::Debug for AbrStreamItem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"AbrStreamItem {{#{} seq{} {}ms {}-{}}}",
self.format.itag,
self.seq,
self.start_ms,
self.offset,
self.offset as usize + self.data.len()
)
}
}
impl From<&InitializedFormat> for FormatInfo {
fn from(value: &InitializedFormat) -> Self {
Self {
itag: value.format_id.itag(),
last_modified: value.format_id.last_modified(),
xtags: value.format_id.xtags.clone(),
}
}
}
impl AbrStream {
pub fn new(options: AbrStreamOptions) -> Result<Self, AbrError> {
let ustreamer_config = BASE64URL
.decode(options.ustreamer_config.as_bytes())
.map_err(|e| {
AbrError::Invalid(format!("could not parse ustreamer_config: {e}").into())
})?;
let po_token = match options.po_token {
Some(pot) => Some(parse_po_token(pot)?),
None => None,
};
let mut abr_state = options.abr_state;
if !abr_state.has_last_manual_direction() {
abr_state.set_last_manual_direction(0);
}
if !abr_state.has_time_since_last_manual_format_selection_ms() {
abr_state.set_time_since_last_manual_format_selection_ms(0);
}
if !abr_state.has_player_time_ms() {
abr_state.set_player_time_ms(options.player_time_ms);
}
if !abr_state.has_visibility() {
abr_state.set_visibility(0);
}
if !abr_state.has_enabled_track_types_bitfield() {
abr_state.set_enabled_track_types_bitfield(i32::from(options.video_streams.is_empty()));
}
if !abr_state.has_audio_track_id() {
if let Some(audio_track) = options.audio_streams.first().and_then(|s| s.track.as_ref())
{
abr_state.set_audio_track_id(audio_track.id.to_owned());
}
}
if let Some(v) = options.video_streams.first() {
if !abr_state.has_last_manual_selected_resolution() {
abr_state.set_last_manual_selected_resolution(v.height as i32);
}
if !abr_state.has_sticky_resolution() {
abr_state.set_sticky_resolution(v.height as i32);
}
}
if !abr_state.has_drc_enabled() {
if let Some(audio) = options.audio_streams.first() {
abr_state.set_drc_enabled(audio.is_drc);
}
}
let audio_format_ids = options
.audio_streams
.iter()
.map(|s| FormatId {
itag: Some(s.itag as i32),
last_modified: s.last_modified,
xtags: s.xtags.clone(),
..Default::default()
})
.collect::<Vec<_>>();
let video_format_ids = options
.video_streams
.iter()
.map(|s| FormatId {
itag: Some(s.itag as i32),
last_modified: s.last_modified,
xtags: s.xtags.clone(),
..Default::default()
})
.collect::<Vec<_>>();
let (cancel_tx, _) = broadcast::channel(1);
Ok(Self {
inner: AbrStreamInner {
http: options.http,
ustreamer_config,
cancel_tx,
vars: RwLock::new(AbrStreamVars {
url: options.url,
po_token,
abr_state,
audio_format_ids,
video_format_ids,
last_seek: Instant::now(),
last_format_selection: Instant::now(),
formats_by_key: HashMap::new(),
playback_cookie: None,
}),
}
.into(),
})
}
fn consumer(&self) -> AbrStreamConsumer {
AbrStreamConsumer {
stream: self.clone(),
header_id_to_format_key_map: HashMap::new(),
sequence_lists: HashMap::new(),
previous_sequences: HashMap::new(),
backoff_time_ms: 0,
buffer: BytesBuffer::new(),
last_header: None,
}
}
pub fn cancel_current(&self) {
let _ = self.inner.cancel_tx.send(());
}
pub fn set_po_token(&self, po_token: &str) -> Result<(), AbrError> {
let pot = parse_po_token(po_token)?;
let mut vars = self.inner.vars.write().unwrap();
vars.po_token = Some(pot);
Ok(())
}
pub fn set_audio_format_ids(&self, audio_streams: &[&AudioStream]) {
let audio_format_ids = audio_streams
.iter()
.map(|s| FormatId {
itag: Some(s.itag as i32),
last_modified: s.last_modified,
xtags: s.xtags.clone(),
..Default::default()
})
.collect::<Vec<_>>();
let mut vars = self.inner.vars.write().unwrap();
vars.audio_format_ids = audio_format_ids;
if let Some(audio) = audio_streams.first() {
vars.abr_state.set_drc_enabled(audio.is_drc);
vars.abr_state.audio_track_id = audio.track.as_ref().map(|t| t.id.to_owned());
}
vars.last_format_selection = Instant::now();
}
pub fn set_video_format_ids(&self, video_streams: &[&VideoStream]) {
let video_format_ids = video_streams
.iter()
.map(|s| FormatId {
itag: Some(s.itag as i32),
last_modified: s.last_modified,
xtags: s.xtags.clone(),
..Default::default()
})
.collect::<Vec<_>>();
let mut vars = self.inner.vars.write().unwrap();
vars.video_format_ids = video_format_ids;
if let Some(v) = video_streams.first() {
vars.abr_state
.set_last_manual_selected_resolution(v.height as i32);
vars.abr_state.set_sticky_resolution(v.height as i32);
}
if vars.abr_state.has_enabled_track_types_bitfield() {
vars.abr_state
.set_enabled_track_types_bitfield(i32::from(video_streams.is_empty()));
}
vars.last_format_selection = Instant::now();
}
pub fn set_player_time_ms(&self, v: i64) {
{
let mut vars = self.inner.vars.write().unwrap();
vars.abr_state.set_player_time_ms(v);
vars.last_seek = Instant::now();
}
self.cancel_current();
}
pub fn stream(&self) -> impl Stream<Item = Result<AbrStreamItem, AbrError>> {
let mut c = self.consumer();
let mut rx = self.inner.cancel_tx.subscribe();
let stream = try_stream! {
let mut ri = 1;
'main: loop {
'fetch: {
let resp = tokio::select! {
_ = rx.recv() => {
tracing::debug!("interrupted during fetch");
c.buffer = BytesBuffer::new();
c.previous_sequences = HashMap::new();
c.last_header = None;
break 'fetch;
}
resp = c.fetch_stream_data() => resp
}?;
let mut data_stream = resp.bytes_stream();
let mut page_len = 0;
// c.sequence_lists.clear();
'chunks: loop {
let chunk = tokio::select! {
_ = rx.recv() => {
tracing::debug!("interrupted during chunk");
break 'fetch;
}
chunk = data_stream.next() => chunk
};
if let Some(chunk) = chunk {
let chunk = chunk?;
let clen = chunk.len();
page_len += clen;
tracing::debug!("chunk {clen}");
c.buffer.push(chunk);
'chunk: loop {
if c.last_header.is_none() {
c.last_header = c.parse_ump_header();
}
tracing::debug!("loop {:?}", c.last_header);
if c.last_header.is_none() {
break 'chunk;
}
let (x, y) = c.process_ump_part()?;
if let Some(item) = x {
yield item;
}
if y {
break 'chunk;
}
}
} else {
break 'chunks;
}
}
tracing::debug!("request #{ri} fetched {page_len} bytes");
{
let main_format = c.main_format()?;
let mut vars = c.stream.inner.vars.write().unwrap();
let mut player_time = vars.abr_state.player_time_ms();
let main_format_sl = c.sequence_lists.get(&get_format_key(&main_format.format_id));
if let Some(main_format_sl) =main_format_sl {
player_time += main_format_sl.iter().fold(0, |acc, seq| {
acc + i64::from(seq.duration_ms.unwrap_or_default())
});
vars.abr_state.set_player_time_ms(player_time);
}
tracing::debug!(
"player time: {player_time}; sequence_count={}, last={:?}",
main_format.sequence_count.unwrap_or_default(),
main_format_sl.and_then(|x| x.last()).and_then(|x| x.sequence_number),
);
}
if c.is_done() {
break 'main;
}
}
if c.backoff_time_ms > 0 {
tracing::debug!("backoff: {} ms", c.backoff_time_ms);
tokio::time::sleep(std::time::Duration::from_millis(
c.backoff_time_ms.try_into().unwrap_or_default(),
)).await;
c.backoff_time_ms = 0;
}
ri += 1;
}
};
Box::pin(stream)
}
}
impl AbrStreamConsumer {
fn read_varint(&mut self, offset: usize) -> (u64, usize) {
// Determine the length of the val
let buffer_len = self.buffer.remaining().saturating_sub(offset);
if buffer_len == 0 {
return (0, 0);
}
let fb = self.buffer[offset];
let byte_length = if fb < 128 {
1
} else if fb < 192 {
2
} else if fb < 224 {
3
} else if fb < 240 {
4
} else {
5
};
if buffer_len < byte_length {
return (0, 0);
}
let n = match byte_length {
1 => self.buffer[offset].into(),
2 => {
let b1 = u64::from(self.buffer[offset]);
let b2 = u64::from(self.buffer[offset + 1]);
(b1 & 0x3f) + 64 * b2
}
3 => {
let b1 = u64::from(self.buffer[offset]);
let b2 = u64::from(self.buffer[offset + 1]);
let b3 = u64::from(self.buffer[offset + 2]);
(b1 & 0x1f) + 32 * (b2 + 256 * b3)
}
4 => {
let b1 = u64::from(self.buffer[offset]);
let b2 = u64::from(self.buffer[offset + 1]);
let b3 = u64::from(self.buffer[offset + 2]);
let b4 = u64::from(self.buffer[offset + 3]);
(b1 & 0x0f) + 16 * (b2 + 256 * (b3 + 256 * b4))
}
_ => u32::from_be_bytes([
self.buffer[offset + 1],
self.buffer[offset + 2],
self.buffer[offset + 3],
self.buffer[offset + 4],
])
.into(),
};
(n, offset + byte_length)
}
fn parse_ump_header(&mut self) -> Option<UmpHeader> {
let (part_type, o1) = self.read_varint(0);
if o1 == 0 {
return None;
}
let (part_size, o2) = self.read_varint(o1);
if o2 == 0 {
return None;
}
self.buffer.advance(o2);
Some(UmpHeader {
part_type: part_type.into(),
size: part_size,
header_id: None,
})
}
fn process_ump_part(&mut self) -> Result<(Option<AbrStreamItem>, bool), AbrError> {
if let Some(last_header) = self.last_header.clone() {
let hsize = last_header.size as usize;
if last_header.part_type == PartType::MEDIA {
// Media data may be processed if the part is only partially downloaded
self.process_media_data().map(|x| {
let eod = x.is_none();
(x, eod)
})
} else if self.buffer.remaining() < hsize {
// Wait for the entire part to be downloaded
tracing::info!("waiting for entire part");
Ok((None, true))
} else {
let part_data = self.buffer.copy_to_bytes(hsize);
match last_header.part_type {
PartType::MEDIA_HEADER => self.process_media_header(&part_data)?,
PartType::MEDIA_END => self.process_media_end(&part_data),
PartType::NEXT_REQUEST_POLICY => {
self.process_next_request_policy(&part_data)?
}
PartType::RELOAD_PLAYER_RESPONSE => self.process_reload_player(&part_data)?,
PartType::FORMAT_INITIALIZATION_METADATA => {
self.process_format_initialization(&part_data)?
}
PartType::SABR_ERROR => {
return Err(AbrError::Sabr(SabrError::parse_from_bytes(&part_data)?));
}
PartType::SABR_REDIRECT => self.process_sabr_redirect(&part_data)?,
PartType::STREAM_PROTECTION_STATUS => {
let prot = StreamProtectionStatus::parse_from_bytes(&part_data)?;
match prot.status() {
1 => tracing::debug!("[StreamProtectionStatus]: Good"),
2 => tracing::warn!("[StreamProtectionStatus]: Attestation pending"),
3 => return Err(AbrError::Attestation),
_ => {}
}
}
_ => {}
};
self.last_header = None;
Ok((None, false))
}
} else {
Ok((None, true))
}
}
fn process_media_header(&mut self, data: &[u8]) -> Result<(), AbrError> {
let mh = MediaHeader::parse_from_bytes(data)?;
let format_id = mh
.format_id
.as_ref()
.ok_or(AbrError::Invalid("media header: no format_id".into()))?;
let header_id = mh
.header_id
.and_then(|hid| u8::try_from(hid).ok())
.ok_or(AbrError::Invalid("media header: no header_id".into()))?;
let format_key = get_format_key(format_id);
let mut vars = self.stream.inner.vars.write().unwrap();
if !vars.formats_by_key.contains_key(&format_key) {
vars.formats_by_key
.insert(format_key.to_owned(), InitializedFormat::try_from(&mh)?);
}
let current_format = vars.formats_by_key.get_mut(&format_key).unwrap();
// This is a hacky workaround to prevent duplicate sequences from being added. This should be fixed in the future
// (preferably by figuring out how to make the server not send duplicates).
if let Some(sequence_number) = mh.sequence_number {
let pseq = self
.previous_sequences
.entry(format_key.to_owned())
.or_default();
if !pseq.insert(sequence_number) {
tracing::warn!("duplicate sequence {sequence_number}");
return Ok(());
}
}
// Save the header's ID so we can identify its stream data later.
self.header_id_to_format_key_map
.entry(header_id)
.or_insert(format_key.to_owned());
let sequence_list = self.sequence_lists.entry(format_key).or_default();
if sequence_list
.iter()
.all(|x| x.sequence_number != Some(mh.sequence_number()))
{
if mh.has_sequence_number() {
current_format.state.set_duration_ms(
current_format.state.duration_ms() + i64::from(mh.duration_ms()),
);
current_format
.state
.set_end_segment_index(current_format.state.end_segment_index() + 1);
}
let seq = Sequence {
itag: mh.itag,
format_id: format_id.clone(),
is_init_segment: mh.is_init_seg(),
duration_ms: mh.duration_ms,
start_ms: mh.start_ms,
start_data_range: mh.start_data_range,
sequence_number: mh.sequence_number,
content_length: mh.content_length,
time_range: mh.time_range.into_option(),
bytes_written: 0,
};
// TODO: testing
let sname = if mh.itag == Some(251) {
"audio"
} else {
"video"
};
tracing::debug!(
"{sname} #{}: content_length={} start_data_range={} time_start={} time_duration={}",
seq.sequence_number.unwrap_or_default(),
seq.content_length.unwrap_or_default(),
seq.start_data_range.unwrap_or_default(),
seq.time_range
.as_ref()
.and_then(|t| t.start)
.unwrap_or_default(),
seq.time_range
.as_ref()
.and_then(|t| t.duration)
.unwrap_or_default(),
);
sequence_list.push(seq);
}
Ok(())
}
fn process_media_data(&mut self) -> Result<Option<AbrStreamItem>, AbrError> {
if let Some(last_header) = &mut self.last_header {
if let Some(header_id) = last_header.header_id.or_else(|| {
let x = self.buffer.try_get_u8().ok();
if x.is_some() {
last_header.header_id = x;
last_header.size -= 1;
}
x
}) {
let to_copy = self.buffer.remaining().min(last_header.size as usize);
if to_copy == 0 {
return Ok(None);
}
let vars = self.stream.inner.vars.read().unwrap();
let format_key = self
.header_id_to_format_key_map
.get(&header_id)
.ok_or_else(|| {
AbrError::Invalid(format!("media: unknown header id {header_id}").into())
})?;
let current_format = vars
.formats_by_key
.get(format_key)
.ok_or(AbrError::Invalid("no current format".into()))?;
let format = FormatInfo::from(current_format);
let data = self.buffer.copy_to_bytes(to_copy);
last_header.size -= data.len() as u64;
if last_header.size == 0 {
self.last_header = None;
}
let seq = self
.sequence_lists
.get_mut(format_key)
.and_then(|x| x.last_mut())
.ok_or(AbrError::Invalid("no current sequence".into()))?;
let offset = seq.start_data_range.unwrap_or_default() + seq.bytes_written;
seq.bytes_written += data.len() as i32;
Ok(Some(AbrStreamItem {
format,
offset,
seq: seq.sequence_number.unwrap_or_default(),
start_ms: seq.start_ms.unwrap_or_default(),
data,
}))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
fn process_media_end(&mut self, data: &[u8]) {
let header_id = data[0];
self.header_id_to_format_key_map.remove(&header_id);
}
fn process_next_request_policy(&mut self, data: &[u8]) -> Result<(), AbrError> {
let mut policy = NextRequestPolicy::parse_from_bytes(data)?;
tracing::debug!("NextRequestPolicy {policy:?}");
self.backoff_time_ms = policy.backoff_time_ms();
let mut vars = self.stream.inner.vars.write().unwrap();
vars.playback_cookie = policy.playback_cookie.take();
Ok(())
}
fn process_reload_player(&mut self, data: &[u8]) -> Result<(), AbrError> {
let reload = ReloadPlayerResponse::parse_from_bytes(data)?;
tracing::debug!("ReloadPlayerResponse {reload:?}");
Ok(())
}
fn process_format_initialization(&mut self, data: &[u8]) -> Result<(), AbrError> {
let init = FormatInitializationMetadata::parse_from_bytes(data)?;
let format = InitializedFormat::try_from(init)?;
let format_key = get_format_key(&format.format_id);
let mut vars = self.stream.inner.vars.write().unwrap();
vars.formats_by_key.insert(format_key, format);
Ok(())
}
fn process_sabr_redirect(&mut self, data: &[u8]) -> Result<(), AbrError> {
let redir = SabrRedirect::parse_from_bytes(data)?;
let url = redir
.url
.ok_or(AbrError::Invalid("sabr redirect: no URL".into()))?;
{
let mut vars = self.stream.inner.vars.write().unwrap();
vars.url = url;
}
Ok(())
}
fn main_format(&self) -> Result<InitializedFormat, AbrError> {
let vars = self.stream.inner.vars.read().unwrap();
let fid = if vars.video_format_ids.is_empty() {
vars.audio_format_ids.first()
} else {
vars.video_format_ids.first()
};
fid.and_then(|fid| {
vars.formats_by_key.values().find(|fmt| {
fmt.format_id.itag == fid.itag && fmt.format_id.last_modified == fid.last_modified
})
})
.or_else(|| {
vars.formats_by_key.values().find(|fmt| {
fmt.mime_type
.as_deref()
.map(|t| t.contains("video"))
.unwrap_or_default()
})
})
.or_else(|| vars.formats_by_key.values().next())
.cloned()
.ok_or_else(|| {
AbrError::Invalid(format!("no main format: {:?}", vars.formats_by_key.values()).into())
})
}
/// A stream is done if all sequences from all formats were received or the last request did not contain any sequences
fn is_done(&self) -> bool {
let vars = self.stream.inner.vars.read().unwrap();
self.sequence_lists.iter().all(|(key, sl)| {
sl.is_empty()
|| vars
.formats_by_key
.get(key)
.and_then(|f| {
f.sequence_count
.zip(sl.last())
.map(|(sc, last)| last.sequence_number == Some(sc.into()))
})
.unwrap_or_default()
})
}
async fn fetch_stream_data(&self) -> Result<reqwest::Response, AbrError> {
let (url, body) = {
let vars = self.stream.inner.vars.read().unwrap();
let mut abr_state = vars.abr_state.clone();
abr_state.set_time_since_last_seek(vars.last_seek.elapsed().as_millis() as i64);
abr_state.set_time_since_last_action_ms(
vars.last_format_selection.elapsed().as_millis() as i64,
);
tracing::debug!(
"fetching stream data: {}ms",
vars.abr_state.player_time_ms()
);
(
vars.url.to_owned(),
VideoPlaybackAbrRequest {
client_abr_state: MessageField::some(abr_state),
selected_format_ids: vars
.formats_by_key
.values()
.map(|f| f.format_id.clone())
.collect(),
buffered_ranges: vars
.formats_by_key
.values()
.map(|f| f.state.clone())
.collect(),
video_playback_ustreamer_config: Some(
self.stream.inner.ustreamer_config.clone(),
),
selected_audio_format_ids: vars.audio_format_ids.clone(),
selected_video_format_ids: vars.video_format_ids.clone(),
streamer_context: MessageField::some(StreamerContext {
client_info: MessageField::some(ClientInfo {
client_name: Some(1),
client_version: Some("2.20250314.01.00".to_owned()),
..Default::default()
}),
po_token: vars.po_token.clone(),
playback_cookie: vars
.playback_cookie
.as_ref()
.and_then(|c| c.write_to_bytes().ok()),
..Default::default()
}),
..Default::default()
},
)
};
let resp = self
.stream
.inner
.http
.post(url)
.body(body.write_to_bytes()?)
.send()
.await?
.error_for_status()?;
Ok(resp)
}
}
fn get_format_key(format_id: &FormatId) -> String {
format!("{};{};", format_id.itag(), format_id.last_modified())
}
fn parse_po_token(po_token: &str) -> Result<Vec<u8>, AbrError> {
BASE64URL
.decode(po_token.as_bytes())
.map_err(|e| AbrError::Invalid(format!("could not parse po_token: {e}").into()))
}
impl TryFrom<&MediaHeader> for InitializedFormat {
type Error = AbrError;
fn try_from(value: &MediaHeader) -> Result<Self, Self::Error> {
let format_id = value
.format_id
.as_ref()
.ok_or(AbrError::Invalid("media header: no format_id".into()))?;
Ok(InitializedFormat {
format_id: format_id.clone(),
duration_ms: value.duration_ms,
mime_type: None,
sequence_count: None,
state: BufferedRange {
format_id: MessageField::some(format_id.clone()),
start_time_ms: Some(0),
duration_ms: Some(0),
start_segment_index: Some(1),
end_segment_index: Some(0),
..Default::default()
},
})
}
}
impl TryFrom<FormatInitializationMetadata> for InitializedFormat {
type Error = AbrError;
fn try_from(value: FormatInitializationMetadata) -> Result<Self, Self::Error> {
let format_id = *value.format_id.0.ok_or(AbrError::Invalid(
"format initialization: no format_id".into(),
))?;
Ok(InitializedFormat {
format_id: format_id.clone(),
duration_ms: value.duration_ms,
mime_type: value.mime_type.clone(),
sequence_count: value.sequence_count,
state: BufferedRange {
format_id: MessageField::some(format_id),
start_time_ms: Some(0),
duration_ms: Some(0),
start_segment_index: Some(1),
end_segment_index: Some(0),
..Default::default()
},
})
}
}
#[cfg(test)]
mod tests {
use std::io::Write;
use futures_util::TryStreamExt;
use rustypipe::{
client::RustyPipe,
model::{AudioCodec, VideoFormat},
param::StreamFilter,
};
use super::*;
#[tokio::test]
#[tracing_test::traced_test]
async fn experiment() {
let rp = RustyPipe::builder().build().unwrap();
let player = rp.query().player("ZeerrnuLi5E").await.unwrap();
let stream_filter = StreamFilter::new()
.allow_abr_only()
.video_max_res(360)
.video_formats([VideoFormat::Webm])
.audio_codecs([AudioCodec::Opus]);
let (_, audio) = player.select_video_audio_stream(&stream_filter);
// let video = video.unwrap();
let audio = audio.unwrap();
let abr = AbrStream::new(AbrStreamOptions {
url: player.abr_streaming_url.clone().unwrap(),
ustreamer_config: player.abr_ustreamer_config.as_deref().unwrap(),
po_token: player.po_token.as_deref(),
audio_streams: &[audio],
// video_streams: &[video],
video_streams: &[],
..Default::default()
})
.unwrap();
{
let mut stream = abr.stream();
// let mut files = HashMap::new();
let mut i = 0;
while let Some(item) = stream.try_next().await.unwrap() {
dbg!(&item);
// let f = files.entry(item.format.itag).or_insert_with(|| {
// std::fs::File::create(format!("test_{}.webm", item.format.itag)).unwrap()
// });
// f.write_all(&item.data).unwrap();
if i == 2 {
abr.set_player_time_ms(60000);
}
i += 1;
}
}
// let audio_md = std::fs::metadata(format!("test_{}.webm", audio.itag)).unwrap();
// assert_eq!(audio_md.len(), audio.size);
// let video_md = std::fs::metadata(format!("test_{}.webm", video.itag)).unwrap();
// assert_eq!(video_md.len(), video.size.unwrap());
}
}

709
downloader/src/abr4.rs Normal file
View file

@ -0,0 +1,709 @@
#![allow(missing_docs)]
use std::{
collections::{HashMap, HashSet},
time::Instant,
};
use async_stream::try_stream;
use bytes::Bytes;
use data_encoding::BASE64URL;
use futures_util::{Stream, StreamExt};
use pin_project_lite::pin_project;
use protobuf::{Message, MessageField};
use reqwest::Client;
use tokio::sync::mpsc;
use rustypipe::model::{traits::YtStream, AudioStream, VideoStream};
use rustypipe_abr_proto::{
buffered_range::BufferedRange,
client_abr_state::ClientAbrState,
common::FormatId,
format_initialization_metadata::FormatInitializationMetadata,
media_header::MediaHeader,
playback_cookie::PlaybackCookie,
sabr_context_sending_policy::SabrContextSendingPolicy,
sabr_context_update::{sabr_context_update::SabrContextWritePolicy, SabrContextUpdate},
streamer_context::{
streamer_context::{ClientInfo, SabrContext},
StreamerContext,
},
video_playback_abr_request::VideoPlaybackAbrRequest,
};
use crate::{
abr_model::{AbrFlowRes, MediaData, UmpHeader, UmpPart},
error::AbrError,
util::BytesBuffer,
};
/*
Features:
- create a stream with start pos in seconds or bytes
- stream: receive packets with media ident + byte range
- evtl: ability to control stream (update position/quality)
*/
pin_project! {
pub struct AbrStream {
http: Client,
url: String,
ustreamer_config: Vec<u8>,
po_token: Option<Vec<u8>>,
audio_stream: AudioStream,
video_stream: Option<VideoStream>,
playback_cookie: Option<PlaybackCookie>,
player_time_ms: i64,
backoff_time_ms: i32,
tx: mpsc::Sender<AbrStreamUpdate>,
rx: mpsc::Receiver<AbrStreamUpdate>,
formats_by_key: HashMap<String, InitializedFormat>,
header_id_to_format_key_map: HashMap<u8, String>,
// previous_sequences: HashMap<String, HashSet<i64>>,
buffer: BytesBuffer,
last_header: Option<UmpHeader>,
sabr_context_updates: HashMap<i32, SabrContextUpdate>,
sabr_contexts_to_send: HashSet<i32>,
time_last_format_selection: Instant,
time_last_seek: Instant,
}
}
#[derive(Clone)]
pub struct AbrStreamController {
tx: mpsc::Sender<AbrStreamUpdate>,
}
#[derive(Clone)]
pub struct AbrStreamOptions<'a> {
pub http: Client,
pub url: String,
pub ustreamer_config: &'a str,
pub po_token: Option<&'a str>,
pub audio_stream: AudioStream,
pub video_stream: Option<VideoStream>,
// pub player_time: i64,
}
pub enum AbrStreamUpdate {
SeekTo(i64),
// SetByteOffset(i64),
// SetAudioStream(AudioStream),
// SetVideoStream(VideoStream),
}
pub struct AbrStreamItem {
pub format: FormatInfo,
pub offset: i32,
pub seq: i64,
pub start_ms: i32,
pub data: Bytes,
}
#[derive(Debug, Clone)]
pub struct FormatInfo {
pub itag: i32,
pub last_modified: u64,
pub xtags: Option<String>,
pub mime_type: Option<String>,
}
#[derive(Debug)]
pub struct InitializedFormat {
pub format_id: FormatId,
pub duration_ms: Option<i32>,
pub mime_type: Option<String>,
pub sequence_count: Option<i32>,
pub sequence_list: Vec<Sequence>,
pub state: BufferedRange,
}
#[derive(Debug, Clone)]
pub struct Sequence {
pub itag: Option<i32>,
pub format_id: FormatId,
pub is_init_segment: bool,
pub duration_ms: Option<i32>,
pub start_ms: Option<i32>,
pub start_data_range: Option<i32>,
pub sequence_number: Option<i64>,
pub content_length: Option<i64>,
pub bytes_written: i32,
}
impl std::fmt::Debug for AbrStreamItem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"AbrStreamItem {{#{} seq{} {}ms {}-{}}}",
self.format.itag,
self.seq,
self.start_ms,
self.offset,
self.offset as usize + self.data.len()
)
}
}
impl From<&InitializedFormat> for FormatInfo {
fn from(value: &InitializedFormat) -> Self {
Self {
itag: value.format_id.itag(),
last_modified: value.format_id.last_modified(),
xtags: value.format_id.xtags.clone(),
mime_type: value.mime_type.clone(),
}
}
}
impl AbrStreamController {
pub async fn send(&mut self, update: AbrStreamUpdate) {
_ = self.tx.send(update).await;
}
}
impl AbrStream {
pub fn new(options: AbrStreamOptions) -> Result<(Self, AbrStreamController), AbrError> {
let ustreamer_config = BASE64URL
.decode(options.ustreamer_config.as_bytes())
.map_err(|e| {
AbrError::Invalid(format!("could not parse ustreamer_config: {e}").into())
})?;
let po_token =
match options.po_token {
Some(pot) => Some(BASE64URL.decode(pot.as_bytes()).map_err(|e| {
AbrError::Invalid(format!("could not parse po_token: {e}").into())
})?),
None => None,
};
let (tx, rx) = mpsc::channel(1);
let now = Instant::now();
Ok((
Self {
http: options.http,
url: options.url,
ustreamer_config,
po_token,
audio_stream: options.audio_stream,
video_stream: options.video_stream,
playback_cookie: None,
player_time_ms: 0,
backoff_time_ms: 0,
tx: tx.clone(),
rx,
formats_by_key: HashMap::new(),
header_id_to_format_key_map: HashMap::new(),
// previous_sequences: HashMap::new(),
buffer: BytesBuffer::new(),
last_header: None,
time_last_seek: now,
time_last_format_selection: now,
sabr_context_updates: HashMap::new(),
sabr_contexts_to_send: HashSet::new(),
},
AbrStreamController { tx },
))
}
pub async fn fetch_stream_data(
&self,
player_time: i64,
buffered_ranges: Vec<BufferedRange>,
) -> Result<reqwest::Response, AbrError> {
tracing::debug!("fetching stream data: {player_time} ms");
let resolution = self.video_stream.as_ref().map(|v| v.height as i32);
let since_last_format_selection =
self.time_last_format_selection.elapsed().as_millis() as i64;
let since_last_seek = self.time_last_seek.elapsed().as_millis() as i64;
let mut selected_format_ids = Vec::new();
if let Some(v) = &self.video_stream {
if buffered_ranges
.iter()
.any(|r| stream_format_eq(v, &r.format_id))
{
selected_format_ids.push(stream_to_format_id(v));
}
}
if buffered_ranges
.iter()
.any(|r| stream_format_eq(&self.audio_stream, &r.format_id))
{
selected_format_ids.push(stream_to_format_id(&self.audio_stream));
}
let body = VideoPlaybackAbrRequest {
client_abr_state: MessageField::some(ClientAbrState {
time_since_last_manual_format_selection_ms: Some(since_last_format_selection),
last_manual_selected_resolution: resolution,
client_viewport_width: Some(1920),
client_viewport_height: Some(1080),
sticky_resolution: resolution,
player_time_ms: Some(player_time),
time_since_last_seek: Some(since_last_seek),
visibility: Some(0),
time_since_last_action_ms: Some(since_last_format_selection.min(since_last_seek)),
enabled_track_types_bitfield: Some(i32::from(self.video_stream.is_none())),
drc_enabled: Some(self.audio_stream.is_drc),
audio_track_id: self.audio_stream.track.as_ref().map(|t| t.id.to_owned()),
..Default::default()
}),
selected_format_ids,
buffered_ranges,
video_playback_ustreamer_config: Some(self.ustreamer_config.clone()),
selected_audio_format_ids: vec![stream_to_format_id(&self.audio_stream)],
selected_video_format_ids: self
.video_stream
.as_ref()
.map(|v| vec![stream_to_format_id(v)])
.unwrap_or_default(),
streamer_context: MessageField::some(StreamerContext {
client_info: MessageField::some(ClientInfo {
client_name: Some(1),
client_version: Some("2.20250314.01.00".to_owned()),
..Default::default()
}),
po_token: self.po_token.clone(),
playback_cookie: self
.playback_cookie
.as_ref()
.and_then(|c| c.write_to_bytes().ok()),
sabr_contexts: self
.sabr_context_updates
.values()
.filter(|v| self.sabr_contexts_to_send.contains(&v.type_()))
.map(|v| SabrContext {
type_: v.type_,
value: v.value.clone(),
..Default::default()
})
.collect(),
unsent_sabr_contexts: self
.sabr_contexts_to_send
.iter()
.copied()
.filter(|v| !self.sabr_context_updates.contains_key(v))
.collect(),
..Default::default()
}),
..Default::default()
};
let resp = self
.http
.post(&self.url)
.body(body.write_to_bytes()?)
.send()
.await?
.error_for_status()?;
Ok(resp)
}
fn clear_sequence_list(&mut self) {
for f in self.formats_by_key.values_mut() {
f.sequence_list.clear();
}
}
fn process_ump_part(&mut self, part: UmpPart) -> Result<Option<AbrStreamItem>, AbrError> {
match part {
UmpPart::MediaHeader(media_header) => {
self.process_media_header(media_header)?;
}
UmpPart::MediaData(media_data) => {
return self.process_media_data(media_data);
}
UmpPart::MediaEnd(header_id) => {
self.header_id_to_format_key_map.remove(&header_id);
}
UmpPart::FormatInit(init) => {
let format = InitializedFormat::try_from(init)?;
let format_key = get_format_key(&format.format_id);
self.formats_by_key.insert(format_key, format);
}
UmpPart::NextRequestPolicy(mut policy) => {
tracing::debug!("NextRequestPolicy {policy:?}");
self.playback_cookie = policy.playback_cookie.take();
self.backoff_time_ms = policy.backoff_time_ms();
}
UmpPart::SabrRedirect(sabr_redirect) => {
self.url = sabr_redirect
.url
.ok_or(AbrError::Invalid("sabr redirect: no URL".into()))?;
tracing::debug!("SABR redirect to {}", self.url);
}
UmpPart::SabrSeek(seek) => {
if let Some((seek_time_ticks, timescale)) = seek.seek_time_ticks.zip(seek.timescale)
{
let seek_to = i64::from(seek_time_ticks) * 1000 / i64::from(timescale);
tracing::debug!("SabrSeek to {seek_to}ms");
self.seek_to(seek_to);
}
}
UmpPart::SabrContextUpdate(sabr_ctx_update) => {
self.process_sabr_context_update(sabr_ctx_update)?;
}
UmpPart::SabrContextSendingPolicy(policy) => {
self.process_sabr_context_sending_policy(policy)?;
}
}
Ok(None)
}
fn process_media_header(&mut self, mh: MediaHeader) -> Result<(), AbrError> {
let format_id = mh
.format_id
.as_ref()
.ok_or(AbrError::Invalid("media header: no format_id".into()))?;
let header_id = mh
.header_id
.and_then(|hid| u8::try_from(hid).ok())
.ok_or(AbrError::Invalid("media header: no header_id".into()))?;
let format_key = get_format_key(format_id);
if !self.formats_by_key.contains_key(&format_key) {
self.formats_by_key
.insert(format_key.to_owned(), InitializedFormat::try_from(&mh)?);
}
let current_format = self.formats_by_key.get_mut(&format_key).unwrap();
// This is a hacky workaround to prevent duplicate sequences from being added. This should be fixed in the future
// (preferably by figuring out how to make the server not send duplicates).
/*
if let Some(sequence_number) = mh.sequence_number {
let pseq = self
.previous_sequences
.entry(format_key.to_owned())
.or_default();
if !pseq.insert(sequence_number) {
tracing::warn!("duplicate sequence {sequence_number}");
return Ok(());
}
}
*/
// Save the header's ID so we can identify its stream data later.
self.header_id_to_format_key_map
.entry(header_id)
.or_insert(format_key);
if current_format
.sequence_list
.iter()
.all(|x| x.sequence_number != Some(mh.sequence_number()))
{
let seq = Sequence {
itag: mh.itag,
format_id: format_id.clone(),
is_init_segment: mh.is_init_seg(),
duration_ms: mh.duration_ms,
start_ms: mh.start_ms,
start_data_range: mh.start_data_range,
sequence_number: mh.sequence_number,
content_length: mh.content_length,
bytes_written: 0,
};
// TODO: testing
let sname = if mh.itag == Some(251) {
"audio"
} else {
"video"
};
tracing::debug!(
"{sname} #{}: content_length={} start_data_range={} time_start={} time_duration={}",
seq.sequence_number.unwrap_or_default(),
seq.content_length.unwrap_or_default(),
seq.start_data_range.unwrap_or_default(),
seq.start_ms.unwrap_or_default(),
seq.duration_ms.unwrap_or_default(),
);
current_format.sequence_list.push(seq);
if let Some(sequence_number) = mh.sequence_number {
let t = i64::from(mh.start_ms()) + i64::from(mh.duration_ms());
self.player_time_ms = t;
current_format.state.set_duration_ms(t);
current_format
.state
.set_end_segment_index(sequence_number as i32);
}
}
Ok(())
}
pub fn process_media_data(
&mut self,
data: MediaData,
) -> Result<Option<AbrStreamItem>, AbrError> {
let format_key = self
.header_id_to_format_key_map
.get(&data.header_id)
.ok_or_else(|| {
AbrError::Invalid(format!("media: unknown header id {}", data.header_id).into())
})?;
let current_format = self
.formats_by_key
.get(format_key)
.ok_or(AbrError::Invalid("no current format".into()))?;
let format = FormatInfo::from(current_format);
if !data.partial {
self.last_header = None;
}
let seq = self
.formats_by_key
.get_mut(format_key)
.unwrap()
.sequence_list
.last_mut()
.ok_or(AbrError::Invalid("no current sequence".into()))?;
let offset = seq.start_data_range.unwrap_or_default() + seq.bytes_written;
seq.bytes_written += data.data.len() as i32;
Ok(Some(AbrStreamItem {
format,
offset,
seq: seq.sequence_number.unwrap_or_default(),
start_ms: seq.start_ms.unwrap_or_default(),
data: data.data,
}))
}
fn process_sabr_context_update(
&mut self,
sabr_ctx_update: SabrContextUpdate,
) -> Result<(), AbrError> {
if let Some((typ, _value)) = sabr_ctx_update
.type_
.as_ref()
.zip(sabr_ctx_update.value.as_ref())
{
if sabr_ctx_update.write_policy()
== SabrContextWritePolicy::SABR_CONTEXT_WRITE_POLICY_KEEP_EXISTING
&& self.sabr_context_updates.contains_key(typ)
{
tracing::debug!("Received a SABR Context Update with write_policy=KEEP_EXISTING matching an existing SABR Context Update. Ignoring update")
} else {
tracing::warn!("Received a SABR Context Update. YouTube is likely trying to force ads on the client.");
tracing::debug!("Registered SabrContextUpdate {sabr_ctx_update:?}");
if sabr_ctx_update.send_by_default() {
self.sabr_contexts_to_send.insert(*typ);
}
self.sabr_context_updates.insert(*typ, sabr_ctx_update);
}
} else {
tracing::warn!("Received an invalid SabrContextUpdate, ignoring")
}
Ok(())
}
fn process_sabr_context_sending_policy(
&mut self,
policy: SabrContextSendingPolicy,
) -> Result<(), AbrError> {
for start_type in policy.start_policy {
if !self.sabr_context_updates.contains_key(&start_type) {
tracing::debug!(
"Server requested to enable SABR Context Update for type {start_type}"
);
self.sabr_contexts_to_send.insert(start_type);
}
}
for stop_type in policy.stop_policy {
if self.sabr_context_updates.contains_key(&stop_type) {
tracing::debug!(
"Server requested to disable SABR Context Update for type {stop_type}"
);
self.sabr_contexts_to_send.remove(&stop_type);
}
}
for discard_type in policy.discard_policy {
if self.sabr_context_updates.contains_key(&discard_type) {
tracing::debug!(
"Server requested to discard SABR Context Update for type {discard_type}"
);
self.sabr_context_updates.remove(&discard_type);
}
}
Ok(())
}
fn main_format(&self) -> Option<&InitializedFormat> {
self.formats_by_key
.values()
.find(|fmt| {
fmt.mime_type
.as_deref()
.map(|t| t.contains("video"))
.unwrap_or_default()
})
.or(self.formats_by_key.values().next())
}
/// A stream is done if all sequences from all formats were received or the last request did not contain any sequences
fn is_done(&self) -> bool {
self.formats_by_key.values().all(|f| {
f.sequence_list.is_empty()
|| f.sequence_count
.zip(f.sequence_list.last())
.map(|(sc, last)| last.sequence_number == Some(sc.into()))
.unwrap_or_default()
})
}
pub fn seek_to(&mut self, seek_to: i64) {
self.player_time_ms = seek_to;
self.time_last_seek = Instant::now();
self.formats_by_key.values_mut().for_each(|f| {
f.state.set_start_time_ms(0);
f.state.set_duration_ms(0);
f.state.set_start_segment_index(1);
f.state.set_end_segment_index(1);
});
self.clear_sequence_list();
// self.main_format().unwrap().sequence_list.last()
}
pub fn stream(mut self) -> impl Stream<Item = Result<AbrStreamItem, AbrError>> {
let stream = try_stream! {
let mut ri = 1;
loop {
let buffered_ranges = self
.formats_by_key
.values()
.map(|f| f.state.clone())
.collect();
let resp = self
.fetch_stream_data(self.player_time_ms, buffered_ranges)
.await?;
let mut data_stream = resp.bytes_stream();
let mut page_len = 0;
self.clear_sequence_list();
while let Some(chunk) = data_stream.next().await {
let chunk = chunk?;
let clen = chunk.len();
page_len += clen;
tracing::debug!("chunk {clen}");
self.buffer.push(chunk);
loop {
if self.last_header.is_none() {
self.last_header = UmpHeader::parse(&mut self.buffer);
}
tracing::debug!("loop {:?}", self.last_header);
if let Some(last_header) = &mut self.last_header {
let pres = UmpPart::parse(&mut self.buffer, last_header);
match pres {
Ok(part) => if let Some(itm) = self.process_ump_part(part)? {
yield itm;
},
Err(AbrFlowRes::Skipped) => {}
Err(AbrFlowRes::EmptyBuffer) => {
break;
}
Err(AbrFlowRes::Error(e)) => Err(e)?,
}
} else {
break;
}
}
}
tracing::debug!("request #{ri} fetched {page_len} bytes");
if let Some(main_format) = self.main_format() {
tracing::debug!(
"player time: {}; sequence_count={}, last={:?}",
self.player_time_ms,
main_format.sequence_count.unwrap_or_default(),
main_format.sequence_list.last().and_then(|x| x.sequence_number),
);
}
if self.is_done() {
break;
}
ri += 1;
}
};
Box::pin(stream)
}
}
fn get_format_key(format_id: &FormatId) -> String {
format!("{};{};", format_id.itag(), format_id.last_modified())
}
fn stream_to_format_id<S: YtStream>(stream: &S) -> FormatId {
FormatId {
itag: Some(stream.itag() as i32),
last_modified: stream.last_modified(),
xtags: stream.xtags().map(|s| s.to_owned()),
..Default::default()
}
}
fn stream_format_eq<S: YtStream>(stream: &S, format: &FormatId) -> bool {
stream.itag() as i32 == format.itag()
&& stream.last_modified() == format.last_modified
&& stream.xtags() == format.xtags.as_deref()
}
impl TryFrom<&MediaHeader> for InitializedFormat {
type Error = AbrError;
fn try_from(value: &MediaHeader) -> Result<Self, Self::Error> {
let format_id = value
.format_id
.as_ref()
.ok_or(AbrError::Invalid("media header: no format_id".into()))?;
Ok(InitializedFormat {
format_id: format_id.clone(),
duration_ms: value.duration_ms,
mime_type: None,
sequence_count: None,
sequence_list: Vec::new(),
state: BufferedRange {
format_id: MessageField::some(format_id.clone()),
start_time_ms: Some(0),
duration_ms: Some(0),
start_segment_index: Some(1),
end_segment_index: Some(0),
..Default::default()
},
})
}
}
impl TryFrom<FormatInitializationMetadata> for InitializedFormat {
type Error = AbrError;
fn try_from(value: FormatInitializationMetadata) -> Result<Self, Self::Error> {
let format_id = *value.format_id.0.ok_or(AbrError::Invalid(
"format initialization: no format_id".into(),
))?;
Ok(InitializedFormat {
format_id: format_id.clone(),
duration_ms: value.duration_ms,
mime_type: value.mime_type.clone(),
sequence_count: value.sequence_count,
sequence_list: Vec::new(),
state: BufferedRange {
format_id: MessageField::some(format_id),
start_time_ms: Some(0),
duration_ms: Some(0),
start_segment_index: Some(1),
end_segment_index: Some(0),
..Default::default()
},
})
}
}

318
downloader/src/abr_model.rs Normal file
View file

@ -0,0 +1,318 @@
use bytes::{Buf, Bytes};
use protobuf::Message;
use rustypipe_abr_proto::{
format_initialization_metadata::FormatInitializationMetadata, media_header::MediaHeader,
next_request_policy::NextRequestPolicy, sabr_context_sending_policy::SabrContextSendingPolicy,
sabr_context_update::SabrContextUpdate, sabr_error::SabrError, sabr_redirect::SabrRedirect,
sabr_seek::SabrSeek, stream_protection_status::StreamProtectionStatus,
};
use crate::{error::AbrError, util::BytesBuffer};
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PartType {
ONESIE_HEADER = 10,
ONESIE_DATA = 11,
MEDIA_HEADER = 20,
MEDIA = 21,
MEDIA_END = 22,
LIVE_METADATA = 31,
HOSTNAME_CHANGE_HINT = 32,
LIVE_METADATA_PROMISE = 33,
LIVE_METADATA_PROMISE_CANCELLATION = 34,
NEXT_REQUEST_POLICY = 35,
USTREAMER_VIDEO_AND_FORMAT_DATA = 36,
FORMAT_SELECTION_CONFIG = 37,
USTREAMER_SELECTED_MEDIA_STREAM = 38,
FORMAT_INITIALIZATION_METADATA = 42,
SABR_REDIRECT = 43,
SABR_ERROR = 44,
SABR_SEEK = 45,
RELOAD_PLAYER_RESPONSE = 46,
PLAYBACK_START_POLICY = 47,
ALLOWED_CACHED_FORMATS = 48,
START_BW_SAMPLING_HINT = 49,
PAUSE_BW_SAMPLING_HINT = 50,
SELECTABLE_FORMATS = 51,
REQUEST_IDENTIFIER = 52,
REQUEST_CANCELLATION_POLICY = 53,
ONESIE_PREFETCH_REJECTION = 54,
TIMELINE_CONTEXT = 55,
REQUEST_PIPELINING = 56,
SABR_CONTEXT_UPDATE = 57,
STREAM_PROTECTION_STATUS = 58,
SABR_CONTEXT_SENDING_POLICY = 59,
LAWNMOWER_POLICY = 60,
SABR_ACK = 61,
END_OF_TRACK = 62,
CACHE_LOAD_POLICY = 63,
LAWNMOWER_MESSAGING_POLICY = 64,
PREWARM_CONNECTION = 65,
UNKNOWN,
}
impl From<u64> for PartType {
fn from(value: u64) -> Self {
match value {
10 => Self::ONESIE_HEADER,
11 => Self::ONESIE_DATA,
20 => Self::MEDIA_HEADER,
21 => Self::MEDIA,
22 => Self::MEDIA_END,
31 => Self::LIVE_METADATA,
32 => Self::HOSTNAME_CHANGE_HINT,
33 => Self::LIVE_METADATA_PROMISE,
34 => Self::LIVE_METADATA_PROMISE_CANCELLATION,
35 => Self::NEXT_REQUEST_POLICY,
36 => Self::USTREAMER_VIDEO_AND_FORMAT_DATA,
37 => Self::FORMAT_SELECTION_CONFIG,
38 => Self::USTREAMER_SELECTED_MEDIA_STREAM,
42 => Self::FORMAT_INITIALIZATION_METADATA,
43 => Self::SABR_REDIRECT,
44 => Self::SABR_ERROR,
45 => Self::SABR_SEEK,
46 => Self::RELOAD_PLAYER_RESPONSE,
47 => Self::PLAYBACK_START_POLICY,
48 => Self::ALLOWED_CACHED_FORMATS,
49 => Self::START_BW_SAMPLING_HINT,
50 => Self::PAUSE_BW_SAMPLING_HINT,
51 => Self::SELECTABLE_FORMATS,
52 => Self::REQUEST_IDENTIFIER,
53 => Self::REQUEST_CANCELLATION_POLICY,
54 => Self::ONESIE_PREFETCH_REJECTION,
55 => Self::TIMELINE_CONTEXT,
56 => Self::REQUEST_PIPELINING,
57 => Self::SABR_CONTEXT_UPDATE,
58 => Self::STREAM_PROTECTION_STATUS,
59 => Self::SABR_CONTEXT_SENDING_POLICY,
60 => Self::LAWNMOWER_POLICY,
61 => Self::SABR_ACK,
62 => Self::END_OF_TRACK,
63 => Self::CACHE_LOAD_POLICY,
64 => Self::LAWNMOWER_MESSAGING_POLICY,
65 => Self::PREWARM_CONNECTION,
_ => Self::UNKNOWN,
}
}
}
/// Header of a UMP part
#[derive(Debug, Clone)]
pub struct UmpHeader {
/// UMP part type
pub part_type: PartType,
/// UMP part size in bytes
pub size: usize,
/// Media stream header id (used to distinguish MEDIA parts from different streams)
pub header_id: Option<u8>,
}
pub enum UmpPart {
MediaHeader(MediaHeader),
MediaData(MediaData),
MediaEnd(u8),
FormatInit(FormatInitializationMetadata),
NextRequestPolicy(NextRequestPolicy),
SabrRedirect(SabrRedirect),
SabrSeek(SabrSeek),
SabrContextUpdate(SabrContextUpdate),
SabrContextSendingPolicy(SabrContextSendingPolicy),
}
pub struct MediaData {
pub header_id: u8,
pub data: Bytes,
pub partial: bool,
}
/// Read a variable-length integer from the given buffer without advancing it
///
/// Returns the integer value and the next offset
pub fn read_varint(buf: &BytesBuffer, offset: usize) -> (u64, usize) {
// Determine the length of the val
let buffer_len = buf.remaining().saturating_sub(offset);
if buffer_len == 0 {
return (0, 0);
}
let fb = buf[offset];
let byte_length = if fb < 128 {
1
} else if fb < 192 {
2
} else if fb < 224 {
3
} else if fb < 240 {
4
} else {
5
};
if buffer_len < byte_length {
return (0, 0);
}
let n = match byte_length {
1 => buf[offset].into(),
2 => {
let b1 = u64::from(buf[offset]);
let b2 = u64::from(buf[offset + 1]);
(b1 & 0x3f) + 64 * b2
}
3 => {
let b1 = u64::from(buf[offset]);
let b2 = u64::from(buf[offset + 1]);
let b3 = u64::from(buf[offset + 2]);
(b1 & 0x1f) + 32 * (b2 + 256 * b3)
}
4 => {
let b1 = u64::from(buf[offset]);
let b2 = u64::from(buf[offset + 1]);
let b3 = u64::from(buf[offset + 2]);
let b4 = u64::from(buf[offset + 3]);
(b1 & 0x0f) + 16 * (b2 + 256 * (b3 + 256 * b4))
}
_ => u32::from_be_bytes([
buf[offset + 1],
buf[offset + 2],
buf[offset + 3],
buf[offset + 4],
])
.into(),
};
(n, offset + byte_length)
}
impl UmpHeader {
/// Try to parse the next UMP header from the given buffer. Only advances the buffer
/// if a complete header can be read.
pub fn parse(buf: &mut BytesBuffer) -> Option<Self> {
let (part_type, o1) = read_varint(buf, 0);
let part_type = PartType::from(part_type);
if o1 == 0 {
return None;
}
let (mut part_size, mut o2) = read_varint(buf, o1);
if o2 == 0 {
return None;
}
let mut header_id = None;
if part_type == PartType::MEDIA && part_size > 0 {
header_id = buf.get(o2);
if header_id.is_none() {
return None;
}
o2 += 1;
part_size -= 1;
}
buf.advance(o2);
Some(Self {
part_type,
size: part_size as usize,
header_id,
})
}
}
pub enum AbrFlowRes {
Skipped,
EmptyBuffer,
Error(AbrError),
}
impl From<AbrError> for AbrFlowRes {
fn from(value: AbrError) -> Self {
Self::Error(value)
}
}
impl From<protobuf::Error> for AbrFlowRes {
fn from(value: protobuf::Error) -> Self {
Self::Error(value.into())
}
}
impl UmpPart {
/// Try to parse the next UMP part from the given buffer. Only advances the buffer
/// if a complete part can be read.
///
/// MediaData parts may be returned partially.
pub fn parse(buf: &mut BytesBuffer, last_header: &mut UmpHeader) -> Result<Self, AbrFlowRes> {
if last_header.part_type == PartType::MEDIA {
let header_id = last_header
.header_id
.ok_or(AbrError::Invalid("no header_id for media part".into()))?;
let to_copy = buf.remaining().min(last_header.size);
let partial = to_copy < last_header.size;
if to_copy == 0 {
return Err(AbrFlowRes::EmptyBuffer);
}
let data = buf.copy_to_bytes(to_copy);
last_header.size -= data.len();
Ok(UmpPart::MediaData(MediaData {
header_id,
data,
partial,
}))
} else if buf.remaining() < last_header.size {
tracing::debug!("waiting for entire part");
Err(AbrFlowRes::EmptyBuffer)
} else {
let part_data = buf.copy_to_bytes(last_header.size);
match last_header.part_type {
PartType::MEDIA_HEADER => Ok(UmpPart::MediaHeader(MediaHeader::parse_from_bytes(
&part_data,
)?)),
PartType::MEDIA_END => Ok(UmpPart::MediaEnd(part_data[0])),
PartType::FORMAT_INITIALIZATION_METADATA => Ok(UmpPart::FormatInit(
FormatInitializationMetadata::parse_from_bytes(&part_data)?,
)),
PartType::NEXT_REQUEST_POLICY => Ok(UmpPart::NextRequestPolicy(
NextRequestPolicy::parse_from_bytes(&part_data)?,
)),
PartType::SABR_REDIRECT => Ok(UmpPart::SabrRedirect(
SabrRedirect::parse_from_bytes(&part_data)?,
)),
PartType::SABR_SEEK => {
Ok(UmpPart::SabrSeek(SabrSeek::parse_from_bytes(&part_data)?))
}
PartType::SABR_CONTEXT_UPDATE => Ok(UmpPart::SabrContextUpdate(
SabrContextUpdate::parse_from_bytes(&part_data)?,
)),
PartType::SABR_CONTEXT_SENDING_POLICY => Ok(UmpPart::SabrContextSendingPolicy(
SabrContextSendingPolicy::parse_from_bytes(&part_data)?,
)),
PartType::SABR_ERROR => {
Err(AbrError::Sabr(SabrError::parse_from_bytes(&part_data)?).into())
}
PartType::RELOAD_PLAYER_RESPONSE => Err(AbrError::ReloadPlayer.into()),
PartType::STREAM_PROTECTION_STATUS => {
let prot = StreamProtectionStatus::parse_from_bytes(&part_data)?;
match prot.status() {
1 => tracing::debug!("[StreamProtectionStatus]: Good"),
2 => tracing::warn!("[StreamProtectionStatus]: Attestation pending"),
3 => return Err(AbrError::Attestation.into()),
_ => {}
};
Err(AbrFlowRes::Skipped)
}
_ => {
tracing::debug!("skipping part {:?}", last_header.part_type);
Err(AbrFlowRes::Skipped)
}
}
}
}
pub fn partial(&self) -> bool {
match self {
UmpPart::MediaData(media_data) => media_data.partial,
_ => false,
}
}
}

View file

@ -0,0 +1,51 @@
use futures_util::TryStreamExt;
use rustypipe::{
client::RustyPipe,
model::{AudioCodec, VideoFormat},
param::StreamFilter,
};
use rustypipe_downloader::abr2::{AbrStream, AbrStreamOptions};
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
let id = std::env::args().into_iter().nth(1).unwrap();
let rp = RustyPipe::builder().build().unwrap();
let player = rp
.query()
.player_from_client(id, rustypipe::client::ClientType::Desktop)
.await
.unwrap();
assert_eq!(player.client_type, rustypipe::client::ClientType::Desktop);
let stream_filter = StreamFilter::new()
.allow_abr_only()
.video_max_res(360)
.video_formats([VideoFormat::Webm])
.audio_codecs([AudioCodec::Opus]);
let (video, audio) = player.select_video_audio_stream(&stream_filter);
let video = video.unwrap();
let audio = audio.unwrap();
dbg!(&audio);
let abr = AbrStream::new(AbrStreamOptions {
url: player.abr_streaming_url.clone().unwrap(),
ustreamer_config: player.abr_ustreamer_config.as_deref().unwrap(),
po_token: player.po_token.as_deref(),
audio_streams: &[audio],
video_streams: &[video],
// video_streams: &[],
..Default::default()
})
.unwrap();
{
let mut stream = abr.stream();
while let Some(itm) = stream.try_next().await.unwrap() {
println!("got {} bytes", itm.data.len());
}
}
}

View file

@ -0,0 +1,97 @@
use std::{fs::File, io::BufReader};
use bytes::Buf;
use rustypipe::{
model::{AudioCodec, VideoFormat, VideoPlayer},
param::StreamFilter,
};
use rustypipe_downloader::abr2::{AbrStream, AbrStreamOptions};
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
let mut args = std::env::args().skip(1);
let player_fname = args.next().expect("player file");
let time_ms = args.next().expect("time in ms").parse::<i64>().unwrap();
/*
let rp = RustyPipe::builder().build().unwrap();
let player = rp.query().player(vid).await.unwrap();
*/
let player: VideoPlayer = {
let pfile = BufReader::new(File::open(player_fname).unwrap());
serde_json::from_reader(pfile).unwrap()
};
let stream_filter = StreamFilter::new()
.allow_abr_only()
.video_max_res(360)
.video_formats([VideoFormat::Webm])
.audio_codecs([AudioCodec::Opus]);
let (_, audio) = player.select_video_audio_stream(&stream_filter);
// let video = video.unwrap();
let audio = audio.unwrap();
let mut abr = AbrStream::new(AbrStreamOptions {
url: player.abr_streaming_url.clone().unwrap(),
ustreamer_config: player.abr_ustreamer_config.as_deref().unwrap(),
po_token: player.po_token.as_deref(),
audio_streams: &[audio],
// video_streams: &[video],
video_streams: &[],
player_time: time_ms,
..Default::default()
})
.unwrap();
let fid = rustypipe_abr_proto::common::FormatId {
itag: Some(251),
last_modified: Some(1714693509531582),
..Default::default()
};
abr.formats_by_key.insert(
"251;1714693509531582;".to_owned(),
rustypipe_downloader::abr2::InitializedFormat {
format_id: fid.clone(),
duration_ms: Some(229301),
mime_type: Some("audio/webm; codecs=\"opus\"".to_owned()),
sequence_count: Some(23),
sequence_list: Vec::new(),
state: rustypipe_abr_proto::buffered_range::BufferedRange {
format_id: Some(fid).into(),
start_time_ms: Some(0),
duration_ms: Some(1),
start_segment_index: Some(1),
end_segment_index: Some(1),
..Default::default()
},
},
);
let resp = abr.fetch_stream_data().await.unwrap();
let data = resp.bytes().await.unwrap();
tracing::debug!("fetched {} bytes", data.len());
abr.buffer.push(data);
loop {
if abr.last_header.is_none() {
abr.last_header = abr.parse_ump_header();
}
tracing::debug!("loop {:?}", abr.last_header);
if abr.last_header.is_none() {
break;
}
let (x, y) = abr.process_ump_part().unwrap();
if let Some(item) = x {
dbg!(&item);
}
if y {
break;
}
}
tracing::info!("buffer remaining: {}", abr.buffer.remaining());
// dbg!(&abr.formats_by_key);
}

View file

@ -0,0 +1,97 @@
use std::{fs::File, io::BufReader};
use bytes::Buf;
use rustypipe::{
model::{AudioCodec, VideoFormat, VideoPlayer},
param::StreamFilter,
};
use rustypipe_downloader::abr2::{AbrStream, AbrStreamOptions};
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
let mut args = std::env::args().skip(1);
let player_fname = args.next().expect("player file");
let time_ms = args.next().expect("time in ms").parse::<i64>().unwrap();
/*
let rp = RustyPipe::builder().build().unwrap();
let player = rp.query().player(vid).await.unwrap();
*/
let player: VideoPlayer = {
let pfile = BufReader::new(File::open(player_fname).unwrap());
serde_json::from_reader(pfile).unwrap()
};
let stream_filter = StreamFilter::new()
.allow_abr_only()
.video_max_res(360)
.video_formats([VideoFormat::Webm])
.audio_codecs([AudioCodec::Opus]);
let (_, audio) = player.select_video_audio_stream(&stream_filter);
// let video = video.unwrap();
let audio = audio.unwrap();
let mut abr = AbrStream::new(AbrStreamOptions {
url: player.abr_streaming_url.clone().unwrap(),
ustreamer_config: player.abr_ustreamer_config.as_deref().unwrap(),
po_token: player.po_token.as_deref(),
audio_streams: &[audio],
// video_streams: &[video],
video_streams: &[],
player_time: time_ms,
..Default::default()
})
.unwrap();
let fid = rustypipe_abr_proto::common::FormatId {
itag: Some(251),
last_modified: Some(1714693509531582),
..Default::default()
};
abr.formats_by_key.insert(
"251;1714693509531582;".to_owned(),
rustypipe_downloader::abr2::InitializedFormat {
format_id: fid.clone(),
duration_ms: Some(229301),
mime_type: Some("audio/webm; codecs=\"opus\"".to_owned()),
sequence_count: Some(23),
sequence_list: Vec::new(),
state: rustypipe_abr_proto::buffered_range::BufferedRange {
format_id: Some(fid).into(),
start_time_ms: Some(0),
duration_ms: Some(1),
start_segment_index: Some(1),
end_segment_index: Some(1),
..Default::default()
},
},
);
let resp = abr.fetch_stream_data().await.unwrap();
let data = resp.bytes().await.unwrap();
tracing::debug!("fetched {} bytes", data.len());
abr.buffer.push(data);
loop {
if abr.last_header.is_none() {
abr.last_header = abr.parse_ump_header();
}
tracing::debug!("loop {:?}", abr.last_header);
if abr.last_header.is_none() {
break;
}
let (x, y) = abr.process_ump_part().unwrap();
if let Some(item) = x {
dbg!(&item);
}
if y {
break;
}
}
tracing::info!("buffer remaining: {}", abr.buffer.remaining());
// dbg!(&abr.formats_by_key);
}

View file

@ -1,6 +1,7 @@
use std::{borrow::Cow, path::PathBuf};
use rustypipe::client::ClientType;
use rustypipe_abr_proto::sabr_error::SabrError;
/// Error from the video downloader
#[derive(thiserror::Error, Debug)]
@ -57,3 +58,22 @@ impl From<image::ImageError> for DownloadError {
Self::AudioTag(value.to_string().into())
}
}
#[derive(thiserror::Error, Debug)]
pub enum AbrError {
/// Error encoding/decoding protobuf
#[error("protobuf: {0}")]
Protobuf(#[from] protobuf::Error),
/// Error from the HTTP client
#[error("http error: {0}")]
Http(#[from] reqwest::Error),
/// Received invalid data
#[error("invalid data: {0}")]
Invalid(Cow<'static, str>),
#[error("sabr error {}: {}", .0.code(), .0.type_())]
Sabr(SabrError),
#[error("attestation required")]
Attestation,
#[error("reload player")]
ReloadPlayer,
}

View file

@ -2,7 +2,14 @@
#![cfg_attr(docsrs, feature(doc_cfg))]
#![warn(missing_docs, clippy::todo, clippy::dbg_macro)]
mod abr;
pub mod abr2;
pub mod abr3;
pub mod abr4;
mod abr_model;
mod error;
mod range_predict;
mod streamtest;
mod util;
use std::{
@ -815,7 +822,7 @@ impl DownloadQuery {
if let Some(v) = video {
downloads.push(StreamDownload {
file: output_path.with_extension(format!("video{}", v.format.extension())),
url: v.url.clone(),
url: v.url.clone().unwrap(),
video_codec: Some(v.codec),
audio_codec: None,
});
@ -823,7 +830,7 @@ impl DownloadQuery {
if let Some(a) = audio {
downloads.push(StreamDownload {
file: output_path.with_extension(format!("audio{}", a.format.extension())),
url: a.url.clone(),
url: a.url.clone().unwrap(),
video_codec: None,
audio_codec: Some(a.codec),
});

View file

@ -0,0 +1,110 @@
use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use crate::abr2::TimeRange;
struct Sx {
stream_len: i64,
header_len: i64,
duration: i64,
positions: BTreeMap<i64, i64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Sequence {
pub itag: i32,
pub is_init_segment: bool,
pub duration_ms: Option<i32>,
pub start_ms: Option<i32>,
pub start_data_range: i32,
pub sequence_number: Option<i64>,
pub content_length: i64,
pub time_range: Option<TimeRange>,
}
impl Sx {
fn new(stream_len: i64, header_len: i64, duration: i64) -> Self {
let mut positions = BTreeMap::new();
positions.insert(header_len, 0);
positions.insert(stream_len - 1, duration);
Self {
stream_len,
header_len,
duration,
positions,
}
}
fn pos_predict(&self, pos: i64) -> i64 {
if pos < self.header_len {
return 0;
}
if pos >= self.stream_len {
panic!("out of range");
}
let blow = *self.positions.keys().rev().find(|x| **x <= pos).unwrap();
let bhigh = *self.positions.keys().find(|x| **x >= pos).unwrap();
let brange = bhigh - blow;
let bperc = pos as f64 / brange as f64;
let tlow = self.positions[&blow];
let thigh = self.positions[&bhigh];
let trange = thigh - tlow;
let tpoint = (trange as f64 * bperc) as i64 + tlow;
(tpoint - 5000).max(tlow)
}
}
#[cfg(test)]
mod tests {
use std::{fs::File, io::BufReader};
use super::{Sequence, Sx};
fn fetch_seg(sequences: &[Sequence], ts: i64) -> &Sequence {
if ts <= 10000 {
&sequences[0]
} else {
sequences
.iter()
.rev()
.find(|s| s.time_range.as_ref().is_some_and(|t| t.start <= ts))
.unwrap()
}
}
#[test]
fn t_range_predict() {
let f = File::open(
"/home/thetadev/Documents/Programmieren/Rust/rustypipe/tmp/abrmap/HSkTpseMd-A_251_nodrc.json",
)
.unwrap();
let sequences: Vec<Sequence> = serde_json::from_reader(BufReader::new(f)).unwrap();
let last = sequences.last().unwrap();
let stream_len = i64::from(last.start_data_range) + last.content_length;
let header_len = sequences[0].content_length;
let ltr = last.time_range.as_ref().unwrap();
let duration = ltr.start + ltr.duration;
let sx = Sx::new(stream_len, header_len, duration);
let max_sl = sequences.iter().map(|s| s.content_length).max().unwrap();
for offset in (0..stream_len).step_by(1000) {
let pred = sx.pos_predict(offset);
let s = fetch_seg(&sequences, pred);
let to_skip = offset - i64::from(s.start_data_range);
if to_skip < 0 {
println!("after target ({to_skip}): offset={offset} pred={pred} s={s:?}");
} else if to_skip <= max_sl {
println!("on target ({to_skip})");
} else {
println!("before target ({to_skip})");
}
}
}
}

View file

@ -0,0 +1,25 @@
use async_stream::stream;
use futures_util::Stream;
fn stream() -> impl Stream<Item = usize> {
stream! {
let mut x = 0;
loop {
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
yield x;
x += 1;
}
};
let (mut __yield_tx, __yield_rx) = unsafe { async_stream::__private::yielder::pair() };
async_stream::__private::AsyncStream::new(__yield_rx, async move {
let mut x = 0;
loop {
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
{
__yield_tx.send(x).await
};
x += 1;
}
})
}

View file

@ -1,5 +1,6 @@
use std::collections::BTreeMap;
use bytes::{Buf, BufMut, Bytes, BytesMut};
use reqwest::Url;
use crate::DownloadError;
@ -22,3 +23,147 @@ pub fn url_to_params(url: &str) -> Result<(Url, BTreeMap<String, String>), Downl
Ok((parsed_url, url_params))
}
#[derive(Default, Debug, Clone)]
pub struct BytesBuffer {
b: Vec<Bytes>,
pos: usize,
}
impl BytesBuffer {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, bytes: Bytes) {
if !bytes.is_empty() {
self.b.push(bytes);
}
}
fn get_ref(&self, index: usize) -> Option<&u8> {
let mut offset = 0;
for p in &self.b {
let pi = index - offset;
if pi < p.len() {
return Some(&p[pi]);
}
offset += p.len();
}
None
}
pub fn get(&self, index: usize) -> Option<u8> {
self.get_ref(index).cloned()
}
pub fn pos(&self) -> usize {
self.pos
}
}
impl std::ops::Index<usize> for BytesBuffer {
type Output = u8;
fn index(&self, index: usize) -> &Self::Output {
self.get_ref(index).expect("index out of range")
}
}
impl Buf for BytesBuffer {
fn remaining(&self) -> usize {
self.b
.iter()
.map(|b| b.remaining())
.fold(0, |a, b| a.saturating_add(b))
}
fn chunk(&self) -> &[u8] {
for p in &self.b {
if p.has_remaining() {
return p.chunk();
}
}
&[]
}
fn advance(&mut self, mut cnt: usize) {
self.pos += cnt;
let mut to_trunc = 0;
for p in &mut self.b {
if cnt == 0 {
break;
}
let to_adv = cnt.min(p.remaining());
p.advance(to_adv);
cnt -= to_adv;
if p.is_empty() {
to_trunc += 1;
}
}
if to_trunc > 0 {
self.b.drain(0..to_trunc);
}
}
fn chunks_vectored<'a>(&'a self, dst: &mut [std::io::IoSlice<'a>]) -> usize {
let mut n = 0;
for p in &self.b {
n += p.chunks_vectored(&mut dst[n..]);
}
n
}
fn copy_to_bytes(&mut self, len: usize) -> bytes::Bytes {
self.pos += len;
if let Some(first) = self.b.first_mut() {
if first.remaining() >= len {
return first.copy_to_bytes(len);
}
}
let rem = self.remaining();
if rem < len {
assert!(len <= rem, "`len` greater than remaining");
}
let mut ret = BytesMut::with_capacity(len);
ret.put(self.take(len));
ret.freeze()
}
}
impl IntoIterator for BytesBuffer {
type Item = u8;
type IntoIter = bytes::buf::IntoIter<BytesBuffer>;
fn into_iter(self) -> Self::IntoIter {
bytes::buf::IntoIter::new(self)
}
}
#[cfg(test)]
mod tests {
use bytes::{Buf, Bytes};
use super::BytesBuffer;
#[test]
fn buffer() {
let mut buf = BytesBuffer::new();
buf.push(Bytes::from_static(b"hello"));
buf.push(Bytes::from_static(b" "));
buf.push(Bytes::from_static(b"world"));
assert_eq!(buf[0], b'h');
buf.advance(5);
let w1 = buf.copy_to_bytes(6);
assert_eq!(w1.as_ref(), b" world");
assert!(buf.b.is_empty());
assert_eq!(buf.pos(), 17);
}
}

View file

@ -1101,3 +1101,10 @@ YouTube playlists may use a commandExecutorCommand which holds a list of command
}
}
```
## [23] YouTube Desktop only returns ABR streams
- **Encountered on:** 16.03.2025
- **Impact:** 🔴 High
- **Endpoint:** player (YT)
- **Status:** Stabilized

View file

@ -44,21 +44,41 @@ struct QPlayer<'a> {
/// Botguard data
#[serde(skip_serializing_if = "Option::is_none")]
service_integrity_dimensions: Option<ServiceIntegrity>,
#[serde(skip_serializing_if = "str::is_empty")]
params: &'a str,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct QPlaybackContext<'a> {
content_playback_context: QContentPlaybackContext<'a>,
device_playback_capabilities: QDevicePlaybackCapabilities,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct QContentPlaybackContext<'a> {
/// Signature timestamp extracted from player.js
signature_timestamp: &'a str,
auto_captions_default_on: bool,
autonav_state: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
current_url: Option<String>,
html5_preference: &'a str,
lact_milliseconds: i8,
/// Referer URL from website
referer: String,
/// Signature timestamp extracted from player.js
signature_timestamp: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
splay: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
vis: Option<i8>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct QDevicePlaybackCapabilities {
supports_vp9_encoding: bool,
support_xhr: bool,
}
#[derive(Debug, Serialize)]
@ -210,8 +230,27 @@ impl RustyPipeQuery {
let playback_context = deobf.as_ref().map(|deobf| QPlaybackContext {
content_playback_context: QContentPlaybackContext {
signature_timestamp: &deobf.sts,
auto_captions_default_on: false,
autonav_state: "STATE_OFF",
current_url: if client_type == ClientType::Tv {
None
} else {
Some(format!("/watch?v={video_id}"))
},
html5_preference: "HTML5_PREF_WANTS",
lact_milliseconds: if client_type == ClientType::Tv {
10
} else {
-1
},
referer: format!("https://www.youtube.com/watch?v={video_id}"),
signature_timestamp: &deobf.sts,
splay: Some(false).filter(|_| client_type != ClientType::Tv),
vis: Some(0).filter(|_| client_type != ClientType::Tv),
},
device_playback_capabilities: QDevicePlaybackCapabilities {
supports_vp9_encoding: true,
support_xhr: true,
},
});
@ -221,6 +260,12 @@ impl RustyPipeQuery {
content_check_ok: true,
racy_check_ok: true,
service_integrity_dimensions: player_po.content_po_token,
params: if client_type == ClientType::Desktop {
// Simulate video preview to circumvent anti-adblock delay
"YAHIAQE%3D"
} else {
""
},
};
self.execute_request_ctx::<response::Player, _, _>(
@ -387,21 +432,6 @@ impl MapResponse<VideoPlayer> for response::Player {
video_details.video_id, ctx.id
)));
}
// Sometimes YouTube Desktop does not output any URLs for adaptive streams.
// Since this is currently rare, it is best to retry the request in this case.
if !is_live
&& !streaming_data.adaptive_formats.c.is_empty()
&& streaming_data
.adaptive_formats
.c
.iter()
.all(|f| f.url.is_none() && f.signature_cipher.is_none())
{
return Err(ExtractionError::Unavailable {
reason: UnavailabilityReason::TryAgain,
msg: "no adaptive stream URLs".to_owned(),
});
}
let video_info = VideoPlayerDetails {
id: video_details.video_id,
@ -417,11 +447,27 @@ impl MapResponse<VideoPlayer> for response::Player {
is_live_content: video_details.is_live_content,
};
let mut mapper = StreamsMapper::new(
ctx.deobf,
ctx.session_po_token.as_ref().map(|t| t.po_token.as_str()),
)?;
let abr_streaming_url = match &streaming_data.server_abr_streaming_url {
Some(url) => match mapper.map_url_abr(url) {
Ok(res) => Some(res),
Err(e) => {
warnings.push(format!("could not map abr_streaming_url: {e}"));
None
}
},
None => None,
};
let abr_ustreamer_config = self.player_config.media_common_config.map(|cfg| {
cfg.media_ustreamer_request_config
.video_playback_ustreamer_config
});
let streams = if !is_live {
let mut mapper = StreamsMapper::new(
ctx.deobf,
ctx.session_po_token.as_ref().map(|t| t.po_token.as_str()),
)?;
mapper.map_streams(streaming_data.formats);
mapper.map_streams(streaming_data.adaptive_formats);
let mut res = mapper.output()?;
@ -530,6 +576,12 @@ impl MapResponse<VideoPlayer> for response::Player {
valid_until,
hls_manifest_url: streaming_data.hls_manifest_url,
dash_manifest_url: streaming_data.dash_manifest_url,
abr_streaming_url,
abr_ustreamer_config,
po_token: ctx
.session_po_token
.as_ref()
.map(|pot| pot.po_token.to_owned()),
preview_frames,
drm,
client_type: ctx.client_type,
@ -693,7 +745,7 @@ impl<'a> StreamsMapper<'a> {
&mut self,
url: &Option<String>,
signature_cipher: &Option<String>,
) -> Result<UrlMapRes, ExtractionError> {
) -> Result<Option<String>, ExtractionError> {
let (url_base, mut url_params) =
match url {
Some(url) => util::url_to_params(url).map_err(|_| {
@ -708,9 +760,7 @@ impl<'a> StreamsMapper<'a> {
)
})
}
None => Err(ExtractionError::InvalidData(
"stream contained neither url or cipher".into(),
)),
None => return Ok(None),
},
}?;
@ -722,10 +772,17 @@ impl<'a> StreamsMapper<'a> {
let url = Url::parse_with_params(url_base.as_str(), url_params.iter())
.map_err(|_| ExtractionError::InvalidData("could not combine URL".into()))?;
Ok(UrlMapRes {
url: url.to_string(),
xtags: url_params.get("xtags").cloned(),
})
Ok(Some(url.to_string()))
}
fn map_url_abr(&mut self, url: &str) -> Result<String, ExtractionError> {
let (url_base, mut url_params) = util::url_to_params(url).map_err(|_| {
ExtractionError::InvalidData(format!("Could not parse url `{url}`").into())
})?;
self.deobf_nsig(&mut url_params)?;
let url = Url::parse_with_params(url_base.as_str(), url_params.iter())
.map_err(|_| ExtractionError::InvalidData("could not combine URL".into()))?;
Ok(url.to_string())
}
fn map_video_stream(&mut self, f: player::Format) -> Result<VideoStream, ExtractionError> {
@ -743,14 +800,14 @@ impl<'a> StreamsMapper<'a> {
format!("invalid video format. itag: {}", f.itag).into(),
));
};
let map_res = self.map_url(&f.url, &f.signature_cipher)?;
Ok(VideoStream {
url: map_res.url,
url: self.map_url(&f.url, &f.signature_cipher)?,
itag: f.itag,
bitrate: f.bitrate,
average_bitrate: f.average_bitrate.unwrap_or(f.bitrate),
size: f.content_length,
last_modified: f.last_modified,
index_range: f.index_range,
init_range: f.init_range,
duration_ms: f.approx_duration_ms,
@ -765,6 +822,7 @@ impl<'a> StreamsMapper<'a> {
format,
codec: get_video_codec(codecs),
mime: f.mime_type,
xtags: f.xtags,
drm_track_type: f.drm_track_type.map(|t| t.into()),
drm_systems: f.drm_families.into_iter().map(|t| t.into()).collect(),
})
@ -783,10 +841,9 @@ impl<'a> StreamsMapper<'a> {
let format = get_audio_format(mtype).ok_or_else(|| {
ExtractionError::InvalidData(format!("invalid audio format. itag: {}", f.itag).into())
})?;
let map_res = self.map_url(&f.url, &f.signature_cipher)?;
Ok(AudioStream {
url: map_res.url,
url: self.map_url(&f.url, &f.signature_cipher)?,
itag: f.itag,
bitrate: f.bitrate,
average_bitrate: f.average_bitrate.unwrap_or(f.bitrate),
@ -795,6 +852,7 @@ impl<'a> StreamsMapper<'a> {
format!("no audio content length. itag: {}", f.itag).into(),
)
})?,
last_modified: f.last_modified,
index_range: f.index_range,
init_range: f.init_range,
duration_ms: f.approx_duration_ms,
@ -803,9 +861,11 @@ impl<'a> StreamsMapper<'a> {
mime: f.mime_type,
channels: f.audio_channels,
loudness_db: f.loudness_db,
is_drc: f.is_drc,
track: f
.audio_track
.map(|t| self.map_audio_track(t, map_res.xtags)),
.map(|t| self.map_audio_track(t, f.xtags.as_deref())),
xtags: f.xtags,
drm_track_type: f.drm_track_type.map(|t| t.into()),
drm_systems: f.drm_families.into_iter().map(|t| t.into()).collect(),
})
@ -814,30 +874,36 @@ impl<'a> StreamsMapper<'a> {
fn map_audio_track(
&mut self,
track: response::player::AudioTrack,
xtags: Option<String>,
xtags: Option<&str>,
) -> AudioTrack {
let mut lang = None;
let mut track_type = None;
if let Some(xtags) = xtags {
xtags
.split(':')
.filter_map(|param| param.split_once('='))
.for_each(|(k, v)| match k {
"lang" => {
lang = Some(v.to_owned());
let xtags_data = urlencoding::decode(xtags)
.ok()
.and_then(|dec| util::b64_decode(dec.as_bytes()).ok())
.and_then(util::kv_from_pb);
match xtags_data {
Some(mut xtags_data) => {
if let Some(acont) = xtags_data.get("acont") {
match serde_plain::from_str(acont) {
Ok(v) => {
track_type = Some(v);
}
Err(_) => {
self.warnings
.push(format!("could not parse audio track type `{acont}`"));
}
}
}
"acont" => match serde_plain::from_str(v) {
Ok(v) => {
track_type = Some(v);
}
Err(_) => {
self.warnings
.push(format!("could not parse audio track type `{v}`"));
}
},
_ => {}
});
lang = xtags_data.remove("lang");
}
None => self
.warnings
.push(format!("could not parse xtags `{xtags}`")),
}
}
AudioTrack {
@ -850,11 +916,6 @@ impl<'a> StreamsMapper<'a> {
}
}
struct UrlMapRes {
url: String,
xtags: Option<String>,
}
fn parse_mime(mime: &str) -> Option<(&str, Vec<&str>)> {
static PATTERN: Lazy<Regex> =
Lazy::new(|| Regex::new(r#"(\w+/\w+);\scodecs="([a-zA-Z-0-9.,\s]*)""#).unwrap());
@ -1013,7 +1074,7 @@ mod tests {
let url = mapper
.map_url(&None, &Some(signature_cipher.to_owned()))
.unwrap()
.url;
.unwrap();
assert_eq!(url, "https://rr5---sn-h0jelnez.googlevideo.com/videoplayback?c=WEB&clen=3781277&dur=229.301&ei=vb7nYvH5BMK8gAfBj7ToBQ&expire=1659376413&fexp=24001373%2C24007246&fvip=5&gir=yes&id=o-AB_BABwrXZJN428ZwDxq5ScPn2AbcGODnRlTVhCQ3mj2&initcwndbps=1588750&ip=2003%3Ade%3Aaf06%3A6300%3Ac750%3A1b77%3Ac74a%3A80e3&itag=251&keepalive=yes&lmt=1655510291473933&lsig=AG3C_xAwRQIgCKCGJ1iu4wlaGXy3jcJyU3inh9dr1FIfqYOZEG_MdmACIQCbungkQYFk7EhD6K2YvLaHFMjKOFWjw001_tLb0lPDtg%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=hH&mime=audio%2Fwebm&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5ednsl&ms=au%2Conr&mt=1659354538&mv=m&mvi=5&n=XzXGSfGusw6OCQ&ns=b_Mq_qlTFcSGlG9RpwpM9xQH&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAPIsKd7-xi4xVHEC9gb__dU4hzfzsHEj9ytd3nt0gEceAiACJWBcw-wFEq9qir35bwKHJZxtQ9mOL7SKiVkLQNDa6A%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=lT-Khi831z8dTejFIRCvCEwx_6romtM&txp=4532434&vprv=1");
}

View file

@ -92,6 +92,7 @@ pub(crate) struct StreamingData {
pub dash_manifest_url: Option<String>,
/// Only on livestreams
pub hls_manifest_url: Option<String>,
pub server_abr_streaming_url: Option<String>,
pub drm_params: Option<String>,
#[serde(default)]
#[serde_as(deserialize_as = "VecSkipError<_>")]
@ -109,8 +110,12 @@ pub(crate) struct Format {
pub format_type: FormatType,
pub mime_type: String,
#[serde_as(as = "Option<DisplayFromStr>")]
pub last_modified: Option<u64>,
#[serde_as(as = "Option<DisplayFromStr>")]
pub content_length: Option<u64>,
pub bitrate: u32,
pub xtags: Option<String>,
pub width: Option<u32>,
pub height: Option<u32>,
@ -122,9 +127,6 @@ pub(crate) struct Format {
#[serde_as(as = "Option<crate::serializer::Range>")]
pub init_range: Option<Range<u32>>,
#[serde_as(as = "Option<DisplayFromStr>")]
pub content_length: Option<u64>,
#[serde(default)]
#[serde_as(deserialize_as = "DefaultOnError")]
pub quality: Option<Quality>,
@ -141,6 +143,8 @@ pub(crate) struct Format {
pub audio_sample_rate: Option<u32>,
pub audio_channels: Option<u8>,
pub loudness_db: Option<f32>,
#[serde(default)]
pub is_drc: bool,
pub audio_track: Option<AudioTrack>,
pub signature_cipher: Option<String>,
@ -294,9 +298,13 @@ pub(crate) struct StoryboardRenderer {
pub spec: String,
}
#[serde_as]
#[derive(Default, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct PlayerConfig {
#[serde(default)]
#[serde_as(deserialize_as = "DefaultOnError")]
pub media_common_config: Option<MediaCommonConfig>,
pub web_drm_config: Option<WebDrmConfig>,
}
@ -306,6 +314,18 @@ pub(crate) struct WebDrmConfig {
pub widevine_service_cert: Option<String>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MediaCommonConfig {
pub media_ustreamer_request_config: UstreamerRequestConfig,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UstreamerRequestConfig {
pub video_playback_ustreamer_config: String,
}
#[derive(Default, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct HeartbeatParams {

View file

@ -61,11 +61,12 @@ VideoPlayer(
),
video_streams: [
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=1619781&dur=163.143&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=17&lmt=1580005480199246&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2F3gpp&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAJ2s7Pm4w42X3u3PWYViDeqIaE2tE9J6oIGpd0KB9gFsAiAH84QaJ4oUNivdRDUBi1ZYI7JSxESsPQ53mLInajKzcQ%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cdur%2Clmt&txp=2211222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=1619781&dur=163.143&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=17&lmt=1580005480199246&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2F3gpp&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAJ2s7Pm4w42X3u3PWYViDeqIaE2tE9J6oIGpd0KB9gFsAiAH84QaJ4oUNivdRDUBi1ZYI7JSxESsPQ53mLInajKzcQ%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cdur%2Clmt&txp=2211222&vprv=1"),
itag: 17,
bitrate: 79452,
average_bitrate: 79428,
size: Some(1619781),
last_modified: Some(1580005480199246),
index_range: None,
init_range: None,
duration_ms: Some(163143),
@ -77,15 +78,17 @@ VideoPlayer(
mime: "video/3gpp; codecs=\"mp4v.20.3, mp4a.40.2\"",
format: r#3gp,
codec: mp4v,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=11439331&dur=163.096&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=18&lmt=1580005476071743&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&pl=37&ratebypass=yes&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAJAH-tWof01vrs8phEoz51XkWwdMzQ77k1UTrdY5XiuTAiA38z-qANX0jtfCiAl4EVMZaKo1ncrzJFRrCffZ6LagrA%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cratebypass%2Cdur%2Clmt&txp=2211222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=11439331&dur=163.096&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=18&lmt=1580005476071743&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&pl=37&ratebypass=yes&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAJAH-tWof01vrs8phEoz51XkWwdMzQ77k1UTrdY5XiuTAiA38z-qANX0jtfCiAl4EVMZaKo1ncrzJFRrCffZ6LagrA%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cratebypass%2Cdur%2Clmt&txp=2211222&vprv=1"),
itag: 18,
bitrate: 561339,
average_bitrate: 561109,
size: Some(11439331),
last_modified: Some(1580005476071743),
index_range: None,
init_range: None,
duration_ms: Some(163096),
@ -97,15 +100,17 @@ VideoPlayer(
mime: "video/mp4; codecs=\"avc1.42001E, mp4a.40.2\"",
format: mp4,
codec: avc1,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&dur=163.096&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=22&lmt=1580005750956837&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&pl=37&ratebypass=yes&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgFlQZgR63Yz9UgY9gVqiyGDVkZmSmACRP3-MmKN7CRzQCIAMHAwZbHmWL1qNH4Nu3A0pXZwErXMVPzMIt-PyxeZqa&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cratebypass%2Cdur%2Clmt&txp=2211222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&dur=163.096&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=22&lmt=1580005750956837&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&pl=37&ratebypass=yes&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgFlQZgR63Yz9UgY9gVqiyGDVkZmSmACRP3-MmKN7CRzQCIAMHAwZbHmWL1qNH4Nu3A0pXZwErXMVPzMIt-PyxeZqa&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cratebypass%2Cdur%2Clmt&txp=2211222&vprv=1"),
itag: 22,
bitrate: 1574434,
average_bitrate: 1574434,
size: None,
last_modified: Some(1580005750956837),
index_range: None,
init_range: None,
duration_ms: Some(163096),
@ -117,17 +122,19 @@ VideoPlayer(
mime: "video/mp4; codecs=\"avc1.64001F, mp4a.40.2\"",
format: mp4,
codec: avc1,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
],
video_only_streams: [
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=1224002&dur=163.029&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=394&keepalive=yes&lmt=1608045375671513&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIgYCPleG9F86UoDRvzFL2xSUUI-HLZGw_P7qBOixlcmKsCIQChdmrJ1NvKo5Ra4QJ9ivR5V8fEcQs0f_3aUiqMhGB5DQ%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&txp=1311222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=1224002&dur=163.029&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=394&keepalive=yes&lmt=1608045375671513&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIgYCPleG9F86UoDRvzFL2xSUUI-HLZGw_P7qBOixlcmKsCIQChdmrJ1NvKo5Ra4QJ9ivR5V8fEcQs0f_3aUiqMhGB5DQ%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&txp=1311222&vprv=1"),
itag: 394,
bitrate: 67637,
average_bitrate: 60063,
size: Some(1224002),
last_modified: Some(1608045375671513),
index_range: Some(Range(
start: 700,
end: 1115,
@ -145,15 +152,17 @@ VideoPlayer(
mime: "video/mp4; codecs=\"av01.0.00M.08\"",
format: mp4,
codec: av01,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=2238952&dur=163.029&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=395&keepalive=yes&lmt=1608045728968690&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAKCXHOCh_P3VlNWebTeWw0WdSln-zYe3BjZeEm2QiltCAiAQNcJBI4G-8dK5z1IUoqBZctk6ddjkl_QYKRFAKXyOcw%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&txp=1311222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=2238952&dur=163.029&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=395&keepalive=yes&lmt=1608045728968690&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAKCXHOCh_P3VlNWebTeWw0WdSln-zYe3BjZeEm2QiltCAiAQNcJBI4G-8dK5z1IUoqBZctk6ddjkl_QYKRFAKXyOcw%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&txp=1311222&vprv=1"),
itag: 395,
bitrate: 135747,
average_bitrate: 109867,
size: Some(2238952),
last_modified: Some(1608045728968690),
index_range: Some(Range(
start: 700,
end: 1115,
@ -171,15 +180,17 @@ VideoPlayer(
mime: "video/mp4; codecs=\"av01.0.00M.08\"",
format: mp4,
codec: av01,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=7808990&dur=163.029&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=134&keepalive=yes&lmt=1580005649163759&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAIjjrMvCEzSLlbvbrjItT4V9JdpggnO5IHye9i4PxTyzAiAmbaFCB2hH7evf9JX3JUx-tU9S6zv2IzSKz8ObGSVRjw%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&txp=2211222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=7808990&dur=163.029&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=134&keepalive=yes&lmt=1580005649163759&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAIjjrMvCEzSLlbvbrjItT4V9JdpggnO5IHye9i4PxTyzAiAmbaFCB2hH7evf9JX3JUx-tU9S6zv2IzSKz8ObGSVRjw%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&txp=2211222&vprv=1"),
itag: 134,
bitrate: 538143,
average_bitrate: 383195,
size: Some(7808990),
last_modified: Some(1580005649163759),
index_range: Some(Range(
start: 740,
end: 1155,
@ -197,15 +208,17 @@ VideoPlayer(
mime: "video/mp4; codecs=\"avc1.4d401e\"",
format: mp4,
codec: avc1,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=4130385&dur=163.029&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=396&keepalive=yes&lmt=1608045761576250&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIgBrQhbygTP6RGjUk0lGbxBI5e3NdeR6C_SW8R_ckZ2PkCIQDaBg5cJxYVWfwRrrELQFgRMOJ4xS3oOOROayoQMjxaCA%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&txp=1311222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=4130385&dur=163.029&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=396&keepalive=yes&lmt=1608045761576250&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIgBrQhbygTP6RGjUk0lGbxBI5e3NdeR6C_SW8R_ckZ2PkCIQDaBg5cJxYVWfwRrrELQFgRMOJ4xS3oOOROayoQMjxaCA%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&txp=1311222&vprv=1"),
itag: 396,
bitrate: 258097,
average_bitrate: 202682,
size: Some(4130385),
last_modified: Some(1608045761576250),
index_range: Some(Range(
start: 700,
end: 1115,
@ -223,15 +236,17 @@ VideoPlayer(
mime: "video/mp4; codecs=\"av01.0.01M.08\"",
format: mp4,
codec: av01,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=6873325&dur=163.029&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=397&keepalive=yes&lmt=1608045990917419&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAMqBb1hKVVzWl3Awrh1T8GQG9IrSWF84zW_ZfjgbAN5QAiAaP3jYyI4ox2aclcOCzYFzqWgByWCxj_FgTN-SfsARXw%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&txp=1311222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=6873325&dur=163.029&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=397&keepalive=yes&lmt=1608045990917419&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAMqBb1hKVVzWl3Awrh1T8GQG9IrSWF84zW_ZfjgbAN5QAiAaP3jYyI4ox2aclcOCzYFzqWgByWCxj_FgTN-SfsARXw%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&txp=1311222&vprv=1"),
itag: 397,
bitrate: 436843,
average_bitrate: 337281,
size: Some(6873325),
last_modified: Some(1608045990917419),
index_range: Some(Range(
start: 700,
end: 1115,
@ -249,15 +264,17 @@ VideoPlayer(
mime: "video/mp4; codecs=\"av01.0.04M.08\"",
format: mp4,
codec: av01,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=22365208&dur=163.046&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=398&keepalive=yes&lmt=1608048380553749&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgR6KqCOoig_FMl2tWKa7qHSmCjIZa9S7ABzEI16qdO2sCIFXccwql4bqV9CHlqXY4tgxyMFUsp7vW4XUjxs3AyG6H&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&txp=1311222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=22365208&dur=163.046&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=398&keepalive=yes&lmt=1608048380553749&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgR6KqCOoig_FMl2tWKa7qHSmCjIZa9S7ABzEI16qdO2sCIFXccwql4bqV9CHlqXY4tgxyMFUsp7vW4XUjxs3AyG6H&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&txp=1311222&vprv=1"),
itag: 398,
bitrate: 1348419,
average_bitrate: 1097369,
size: Some(22365208),
last_modified: Some(1608048380553749),
index_range: Some(Range(
start: 700,
end: 1115,
@ -275,15 +292,17 @@ VideoPlayer(
mime: "video/mp4; codecs=\"av01.0.08M.08\"",
format: mp4,
codec: av01,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=65400181&dur=163.046&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=299&keepalive=yes&lmt=1580005649161486&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRgIhAPjxbuzkozPDc1Nd_0q5X8x8H2SiDvAUFuqqMadtz3SNAiEA_3kXCeePb2kci-WB2779tzI56E6E0iKwoHnUSkKCzwU%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&txp=2211222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=65400181&dur=163.046&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=299&keepalive=yes&lmt=1580005649161486&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRgIhAPjxbuzkozPDc1Nd_0q5X8x8H2SiDvAUFuqqMadtz3SNAiEA_3kXCeePb2kci-WB2779tzI56E6E0iKwoHnUSkKCzwU%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&txp=2211222&vprv=1"),
itag: 299,
bitrate: 4190323,
average_bitrate: 3208919,
size: Some(65400181),
last_modified: Some(1580005649161486),
index_range: Some(Range(
start: 740,
end: 1155,
@ -301,15 +320,17 @@ VideoPlayer(
mime: "video/mp4; codecs=\"avc1.64002a\"",
format: mp4,
codec: avc1,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=42567727&dur=163.046&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=399&keepalive=yes&lmt=1608052932785283&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgFguw-cmBNOQegpyRRzcCScp2WaSnq_o7FB1-AiBgFpICIAGlMj9-kzNCWb3nhpg98Mc239ls6YYyoL8z1QpM8VmL&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&txp=1311222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=42567727&dur=163.046&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=399&keepalive=yes&lmt=1608052932785283&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgFguw-cmBNOQegpyRRzcCScp2WaSnq_o7FB1-AiBgFpICIAGlMj9-kzNCWb3nhpg98Mc239ls6YYyoL8z1QpM8VmL&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&txp=1311222&vprv=1"),
itag: 399,
bitrate: 2572342,
average_bitrate: 2088624,
size: Some(42567727),
last_modified: Some(1608052932785283),
index_range: Some(Range(
start: 700,
end: 1115,
@ -327,17 +348,19 @@ VideoPlayer(
mime: "video/mp4; codecs=\"av01.0.09M.08\"",
format: mp4,
codec: av01,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
],
audio_streams: [
AudioStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=995840&dur=163.189&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=139&keepalive=yes&lmt=1580005582214385&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhALhtrbIL9_CQBeXsEwxFqyPY1XqBCOceQc7y00h7XBS9AiAH9VkMnkuFCU1ACkYU__uApTwcYeDoYNU74VYmKED3Gw%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&txp=2211222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=995840&dur=163.189&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=139&keepalive=yes&lmt=1580005582214385&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhALhtrbIL9_CQBeXsEwxFqyPY1XqBCOceQc7y00h7XBS9AiAH9VkMnkuFCU1ACkYU__uApTwcYeDoYNU74VYmKED3Gw%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&txp=2211222&vprv=1"),
itag: 139,
bitrate: 49724,
average_bitrate: 48818,
size: 995840,
last_modified: Some(1580005582214385),
index_range: Some(Range(
start: 641,
end: 876,
@ -353,15 +376,17 @@ VideoPlayer(
channels: Some(2),
loudness_db: None,
track: None,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
AudioStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=934449&dur=163.061&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=249&keepalive=yes&lmt=1608509101590706&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgPmadKd9As393GMRmu1Ow4RfQkDQhY6SbPRnkLMYyZOoCIE9AIgMMJ7n5HD2gKv3c8-HrnkMeakq_uWUOivnWquJX&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&txp=1311222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=934449&dur=163.061&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=249&keepalive=yes&lmt=1608509101590706&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgPmadKd9As393GMRmu1Ow4RfQkDQhY6SbPRnkLMYyZOoCIE9AIgMMJ7n5HD2gKv3c8-HrnkMeakq_uWUOivnWquJX&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&txp=1311222&vprv=1"),
itag: 249,
bitrate: 53039,
average_bitrate: 45845,
size: 934449,
last_modified: Some(1608509101590706),
index_range: Some(Range(
start: 266,
end: 551,
@ -377,15 +402,17 @@ VideoPlayer(
channels: Some(2),
loudness_db: None,
track: None,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
AudioStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=1245866&dur=163.061&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=250&keepalive=yes&lmt=1608509101111096&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIge8uetzhejNg3DegY9EQkpvVam1gp8Jm-q6oqtb6Rn9wCIQD_VeQle7Z2H1uXB6qsYMGDU4OWA4h6YTTwMDmw5DDvuA%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&txp=1311222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=1245866&dur=163.061&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=250&keepalive=yes&lmt=1608509101111096&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIge8uetzhejNg3DegY9EQkpvVam1gp8Jm-q6oqtb6Rn9wCIQD_VeQle7Z2H1uXB6qsYMGDU4OWA4h6YTTwMDmw5DDvuA%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&txp=1311222&vprv=1"),
itag: 250,
bitrate: 71268,
average_bitrate: 61123,
size: 1245866,
last_modified: Some(1608509101111096),
index_range: Some(Range(
start: 266,
end: 551,
@ -401,15 +428,17 @@ VideoPlayer(
channels: Some(2),
loudness_db: None,
track: None,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
AudioStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=2640283&dur=163.096&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=140&keepalive=yes&lmt=1580005579712232&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAI8YylDImOPxxRo251u_RX6ir_j0p-gP_yQPcI6SxareAiArCxOcgrF9pxYS5bNYEnLGEQxRiEFJ0sT2Ydpa1G7x5A%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&txp=2211222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=2640283&dur=163.096&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=140&keepalive=yes&lmt=1580005579712232&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAI8YylDImOPxxRo251u_RX6ir_j0p-gP_yQPcI6SxareAiArCxOcgrF9pxYS5bNYEnLGEQxRiEFJ0sT2Ydpa1G7x5A%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&txp=2211222&vprv=1"),
itag: 140,
bitrate: 130268,
average_bitrate: 129508,
size: 2640283,
last_modified: Some(1580005579712232),
index_range: Some(Range(
start: 632,
end: 867,
@ -425,15 +454,17 @@ VideoPlayer(
channels: Some(2),
loudness_db: None,
track: None,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
AudioStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=2476314&dur=163.061&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=251&keepalive=yes&lmt=1608509101894140&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIgErpt4HOgIybMGrMD2qg9JB4O53n0jsCxkiI9JBxbz8ECIQDixyFJ54m4NsxhyFtIYPscMVp_G6RyvwrfKzdoya-62Q%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&txp=1311222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=ANDROID&clen=2476314&dur=163.061&ei=q1jpYtOPEYSBgQeHmqbwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AEDMTCojVtwpIKOdhBaxEHE5s322qnAJHGqa2r1F46BM&initcwndbps=1527500&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=251&keepalive=yes&lmt=1608509101894140&lsig=AG3C_xAwRgIhAOiL-qJ04sA8FSOkEJfOYl3gFe4SzwYu_rAf3DMLHYigAiEA0Upi1HqqIu7NH_LTDL0jT1R5TTozQypL5FiSP9RoqtU%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659459429&mv=m&mvi=5&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIgErpt4HOgIybMGrMD2qg9JB4O53n0jsCxkiI9JBxbz8ECIQDixyFJ54m4NsxhyFtIYPscMVp_G6RyvwrfKzdoya-62Q%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&txp=1311222&vprv=1"),
itag: 251,
bitrate: 140633,
average_bitrate: 121491,
size: 2476314,
last_modified: Some(1608509101894140),
index_range: Some(Range(
start: 266,
end: 551,
@ -449,6 +480,7 @@ VideoPlayer(
channels: Some(2),
loudness_db: None,
track: None,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
@ -465,6 +497,9 @@ VideoPlayer(
valid_until: "[date]",
hls_manifest_url: None,
dash_manifest_url: Some("https://manifest.googlevideo.com/api/manifest/dash/expire/1659481355/ei/q1jpYtOPEYSBgQeHmqbwAQ/ip/2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e/id/a4fbddf14c6649b4/source/youtube/requiressl/yes/playback_host/rr5---sn-h0jeenek.googlevideo.com/mh/mQ/mm/31%2C29/mn/sn-h0jeenek%2Csn-h0jelnez/ms/au%2Crdu/mv/m/mvi/5/pl/37/hfr/1/as/fmp4_audio_clear%2Cfmp4_sd_hd_clear/initcwndbps/1527500/vprv/1/mt/1659459429/fvip/4/itag_bl/376%2C377%2C384%2C385%2C612%2C613%2C617%2C619%2C623%2C628%2C655%2C656%2C660%2C662%2C666%2C671/keepalive/yes/fexp/24001373%2C24007246/itag/0/sparams/expire%2Cei%2Cip%2Cid%2Csource%2Crequiressl%2Chfr%2Cas%2Cvprv%2Citag/sig/AOq0QJ8wRAIgMm4a_MIHA3YUszKeruSy3exs5JwNjJAyLAwxL0yPdNMCIANb9GDMSTp_NT-PPhbvYMwRULJ5a9BO6MYD9FuWprC1/lsparams/playback_host%2Cmh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps/lsig/AG3C_xAwRQIgETSOwhwWVMy7gmrFXZlJu655ToLzSwOEsT16oRyrWhACIQDkvOEw1fImz5omu4iVIRNFe-z-JC9v8WUyx281dW2NOw%3D%3D"),
abr_streaming_url: None,
abr_ustreamer_config: Some("Cs0CCAAlMZkqPi0AAIA_NT0Klz9YAXjoAoABAaABAbUB9ijcP-ABAegBA_ABAfkBAAAAAAAA0D-BAgAAAAAAABhAmALwAaAC6AK4AgDaAlUQsOoBGKhGIKCcASjYNjCYdXCIJ4AB9AO4AQHgAQGYAgyoAgGwAgG4AgHAAgHIAgHQAgLgAgHoAgLwAgGAAwaIA4gnmAMBqAMIsAMBuAMBwAMB2AMB-gIrCAwQGBgyIDItAABwQjUAAIxCQAFIAWUAAIBAaMBwzQEAAIA_8AEB6ALgA4IDAJADAaADAbADA-ADkE6wBAG4BAHKBFgKFQiA4gkQmHUYrAIlAAAAACgAMABAARDg1AMY0A8qNgoKdGJfY29zdF81MCAIKQAAAAAAAAAASAFQAV2amZk-ZQAAAD9tAAAAP3UAAAA_eMCpB5IBADAB6AQB8AQB-AQBkAUBGAEgATIMCKsCEI662NuboOcCMgwIjwMQg-na_r_Q7QIyDAiqAhCOutjbm6DnAjIMCI4DEJXUhISv0O0CMgwIhwEQjrrY25ug5wIyDAiNAxCr6siQptDtAjIMCIYBEO_L2NuboOcCMgwIjAMQuvqao6XQ7QIyDAiFARCOutjbm6DnAjIMCIsDEPLf1JOl0O0CMgwIoAEQjrrY25ug5wIyDAiKAxDZmZnro9DtAjIMCIsBEPGp4ruboOcCMgwIjAEQ6M3Jupug5wIyDAj5ARCykfms493tAjIMCPoBELju26zj3e0CMgwI-wEQ_NOLrePd7QI6AA=="),
po_token: None,
preview_frames: [
Frameset(
url_template: "https://i.ytimg.com/sb/pPvd8UxmSbQ/storyboard3_L0/default.jpg?sqp=-oaymwENSDfyq4qpAwVwAcABBqLzl_8DBgjf8LPxBQ==&sigh=rs$AOn4CLAXobPyrylgm8IEvjlZzqYTiPe1Ow",

View file

@ -66,11 +66,12 @@ VideoPlayer(
),
video_streams: [
VideoStream(
url: "https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?c=WEB&clen=11439331&dur=163.096&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=18&lmt=1580005476071743&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=ig_QojS86GYHYg&ns=cOm8mnsR9HFwfq55dDyGyqYH&pl=37&ratebypass=yes&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIgNqstD2C4HG7Vn5En5Z4aUyH2mk7gAB9cyfOAWGCaWeoCIQDbxxJZuOnz_3RJNviFYADvgTO7u8YBYKtpFtp9Ujmk2w%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cratebypass%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=2211222&vprv=1",
url: Some("https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?c=WEB&clen=11439331&dur=163.096&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=18&lmt=1580005476071743&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=ig_QojS86GYHYg&ns=cOm8mnsR9HFwfq55dDyGyqYH&pl=37&ratebypass=yes&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIgNqstD2C4HG7Vn5En5Z4aUyH2mk7gAB9cyfOAWGCaWeoCIQDbxxJZuOnz_3RJNviFYADvgTO7u8YBYKtpFtp9Ujmk2w%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cratebypass%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=2211222&vprv=1"),
itag: 18,
bitrate: 561339,
average_bitrate: 561109,
size: Some(11439331),
last_modified: Some(1580005476071743),
index_range: None,
init_range: None,
duration_ms: Some(163096),
@ -82,17 +83,19 @@ VideoPlayer(
mime: "video/mp4; codecs=\"avc1.42001E, mp4a.40.2\"",
format: mp4,
codec: avc1,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
],
video_only_streams: [
VideoStream(
url: "https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=1484736&dur=163.029&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=278&keepalive=yes&lmt=1608509388295661&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIgDYs7xrSqi-Co90zypk9zutEJ-aaEpNAHWnE57zVIfxgCIQDE0exs9SN8JH5OEJ8728Ke6bfa0CsUucFETHLk3IFF7w%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1",
url: Some("https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=1484736&dur=163.029&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=278&keepalive=yes&lmt=1608509388295661&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIgDYs7xrSqi-Co90zypk9zutEJ-aaEpNAHWnE57zVIfxgCIQDE0exs9SN8JH5OEJ8728Ke6bfa0CsUucFETHLk3IFF7w%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1"),
itag: 278,
bitrate: 87458,
average_bitrate: 72857,
size: Some(1484736),
last_modified: Some(1608509388295661),
index_range: Some(Range(
start: 218,
end: 751,
@ -110,15 +113,17 @@ VideoPlayer(
mime: "video/webm; codecs=\"vp9\"",
format: webm,
codec: vp9,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=1224002&dur=163.029&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=394&keepalive=yes&lmt=1608045375671513&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAI-uoNLUkMHpH35niVh1tBvwwFLtmSbeHyknmyCvccFVAiB2XriyJd0u2q-tGIRTx5qtKt6bJCs5ndXtMsdSxOheuA%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1",
url: Some("https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=1224002&dur=163.029&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=394&keepalive=yes&lmt=1608045375671513&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAI-uoNLUkMHpH35niVh1tBvwwFLtmSbeHyknmyCvccFVAiB2XriyJd0u2q-tGIRTx5qtKt6bJCs5ndXtMsdSxOheuA%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1"),
itag: 394,
bitrate: 67637,
average_bitrate: 60063,
size: Some(1224002),
last_modified: Some(1608045375671513),
index_range: Some(Range(
start: 700,
end: 1115,
@ -136,15 +141,17 @@ VideoPlayer(
mime: "video/mp4; codecs=\"av01.0.00M.08\"",
format: mp4,
codec: av01,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=2973283&dur=163.029&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=242&keepalive=yes&lmt=1608509388282028&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIgEleuqkeo7x7BsHur5aGPfHaT6KjKEG4c1d_xXwqlrsYCIQD85X_m050XwWyYlfLiWtZz-TX--H8H0UvfZCWKpY7m4Q%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1",
url: Some("https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=2973283&dur=163.029&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=242&keepalive=yes&lmt=1608509388282028&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIgEleuqkeo7x7BsHur5aGPfHaT6KjKEG4c1d_xXwqlrsYCIQD85X_m050XwWyYlfLiWtZz-TX--H8H0UvfZCWKpY7m4Q%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1"),
itag: 242,
bitrate: 184064,
average_bitrate: 145902,
size: Some(2973283),
last_modified: Some(1608509388282028),
index_range: Some(Range(
start: 219,
end: 753,
@ -162,15 +169,17 @@ VideoPlayer(
mime: "video/webm; codecs=\"vp9\"",
format: webm,
codec: vp9,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=2238952&dur=163.029&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=395&keepalive=yes&lmt=1608045728968690&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAIBttTR02kTdGb4vdxQ9Gro88JOAY7u5z69nJbdmVS1sAiBr61rqkUtra4PHLdnp2w-s8ZSaN_4qZ3OEeeuIr5C13w%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1",
url: Some("https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=2238952&dur=163.029&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=395&keepalive=yes&lmt=1608045728968690&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAIBttTR02kTdGb4vdxQ9Gro88JOAY7u5z69nJbdmVS1sAiBr61rqkUtra4PHLdnp2w-s8ZSaN_4qZ3OEeeuIr5C13w%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1"),
itag: 395,
bitrate: 135747,
average_bitrate: 109867,
size: Some(2238952),
last_modified: Some(1608045728968690),
index_range: Some(Range(
start: 700,
end: 1115,
@ -188,15 +197,17 @@ VideoPlayer(
mime: "video/mp4; codecs=\"av01.0.00M.08\"",
format: mp4,
codec: av01,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=7808990&dur=163.029&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=134&keepalive=yes&lmt=1580005649163759&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRgIhAMBRhMAZ5GXFSZHN6D-XhXRdG_EWSNwnN2eLPlwVNQ6PAiEA75eH0iJLgwRkujaABZnaJxG2ni-4irYHEGD42x6uaQg%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=2211222&vprv=1",
url: Some("https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=7808990&dur=163.029&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=134&keepalive=yes&lmt=1580005649163759&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRgIhAMBRhMAZ5GXFSZHN6D-XhXRdG_EWSNwnN2eLPlwVNQ6PAiEA75eH0iJLgwRkujaABZnaJxG2ni-4irYHEGD42x6uaQg%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=2211222&vprv=1"),
itag: 134,
bitrate: 538143,
average_bitrate: 383195,
size: Some(7808990),
last_modified: Some(1580005649163759),
index_range: Some(Range(
start: 740,
end: 1155,
@ -214,15 +225,17 @@ VideoPlayer(
mime: "video/mp4; codecs=\"avc1.4d401e\"",
format: mp4,
codec: avc1,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=5169510&dur=163.029&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=243&keepalive=yes&lmt=1608509388282405&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgNi0fwQbep6oKsEeEGfms2Ay4x2OL2G0hUX5GFhycgKkCIANiC-j-Gz3-noxsNeSKKPxy--T9mFBu_8V7Vi5-zDYS&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1",
url: Some("https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=5169510&dur=163.029&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=243&keepalive=yes&lmt=1608509388282405&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgNi0fwQbep6oKsEeEGfms2Ay4x2OL2G0hUX5GFhycgKkCIANiC-j-Gz3-noxsNeSKKPxy--T9mFBu_8V7Vi5-zDYS&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1"),
itag: 243,
bitrate: 319085,
average_bitrate: 253673,
size: Some(5169510),
last_modified: Some(1608509388282405),
index_range: Some(Range(
start: 220,
end: 754,
@ -240,15 +253,17 @@ VideoPlayer(
mime: "video/webm; codecs=\"vp9\"",
format: webm,
codec: vp9,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=4130385&dur=163.029&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=396&keepalive=yes&lmt=1608045761576250&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgFuBoOIkqwq0D1_OmnNJx3C0jmhHUyskpzPrTMoaWRYECIFZ1Y4QbQ41GsWS8yRHox8l_nGVosfXhXfKu3v18AyeT&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1",
url: Some("https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=4130385&dur=163.029&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=396&keepalive=yes&lmt=1608045761576250&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgFuBoOIkqwq0D1_OmnNJx3C0jmhHUyskpzPrTMoaWRYECIFZ1Y4QbQ41GsWS8yRHox8l_nGVosfXhXfKu3v18AyeT&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1"),
itag: 396,
bitrate: 258097,
average_bitrate: 202682,
size: Some(4130385),
last_modified: Some(1608045761576250),
index_range: Some(Range(
start: 700,
end: 1115,
@ -266,15 +281,17 @@ VideoPlayer(
mime: "video/mp4; codecs=\"av01.0.01M.08\"",
format: mp4,
codec: av01,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=8890590&dur=163.029&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=244&keepalive=yes&lmt=1608509388284632&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgMYF0KQQNgYI8oOhgdCwyRY6E_hvFnJiaAadyMf89MRoCIHnDnROTvUoy0iIBM3MzFAxJh_bLA-2vFl9KFDrHOf1B&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1",
url: Some("https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=8890590&dur=163.029&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=244&keepalive=yes&lmt=1608509388284632&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgMYF0KQQNgYI8oOhgdCwyRY6E_hvFnJiaAadyMf89MRoCIHnDnROTvUoy0iIBM3MzFAxJh_bLA-2vFl9KFDrHOf1B&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1"),
itag: 244,
bitrate: 539056,
average_bitrate: 436270,
size: Some(8890590),
last_modified: Some(1608509388284632),
index_range: Some(Range(
start: 220,
end: 754,
@ -292,15 +309,17 @@ VideoPlayer(
mime: "video/webm; codecs=\"vp9\"",
format: webm,
codec: vp9,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=6873325&dur=163.029&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=397&keepalive=yes&lmt=1608045990917419&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRgIhAOtLGFoFtLHIXzNRoSrR7ULbIz91OYmaVQkcSatqNKAiAiEA23ZF7h2BZZCAGc0Zdd2p3PWRotmwLDyH6yYCuQpE8xw%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1",
url: Some("https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=6873325&dur=163.029&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=397&keepalive=yes&lmt=1608045990917419&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRgIhAOtLGFoFtLHIXzNRoSrR7ULbIz91OYmaVQkcSatqNKAiAiEA23ZF7h2BZZCAGc0Zdd2p3PWRotmwLDyH6yYCuQpE8xw%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1"),
itag: 397,
bitrate: 436843,
average_bitrate: 337281,
size: Some(6873325),
last_modified: Some(1608045990917419),
index_range: Some(Range(
start: 700,
end: 1115,
@ -318,15 +337,17 @@ VideoPlayer(
mime: "video/mp4; codecs=\"av01.0.04M.08\"",
format: mp4,
codec: av01,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=16547577&dur=163.029&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=247&keepalive=yes&lmt=1608509388326822&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIgfYKbT_196P-2EtjuqcTKdataiM480y65Ko0a73dv7WECIQC6nqWienQvu7swC1OW9HlwFWRH7VwTwj6H4yjY6FYvzg%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1",
url: Some("https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=16547577&dur=163.029&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=247&keepalive=yes&lmt=1608509388326822&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIgfYKbT_196P-2EtjuqcTKdataiM480y65Ko0a73dv7WECIQC6nqWienQvu7swC1OW9HlwFWRH7VwTwj6H4yjY6FYvzg%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1"),
itag: 247,
bitrate: 982813,
average_bitrate: 812006,
size: Some(16547577),
last_modified: Some(1608509388326822),
index_range: Some(Range(
start: 220,
end: 754,
@ -344,15 +365,17 @@ VideoPlayer(
mime: "video/webm; codecs=\"vp9\"",
format: webm,
codec: vp9,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=35955780&dur=163.046&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=302&keepalive=yes&lmt=1608509234088626&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgQG8GPj3w_5_Lr2apagmte66IFBY3bYcZ2KnhwnUpshYCIFgvHYIZsz8WdYGSk9adpfMNKX0pzSP_l8cW47Gq2RTi&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1",
url: Some("https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=35955780&dur=163.046&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=302&keepalive=yes&lmt=1608509234088626&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgQG8GPj3w_5_Lr2apagmte66IFBY3bYcZ2KnhwnUpshYCIFgvHYIZsz8WdYGSk9adpfMNKX0pzSP_l8cW47Gq2RTi&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1"),
itag: 302,
bitrate: 2354009,
average_bitrate: 1764202,
size: Some(35955780),
last_modified: Some(1608509234088626),
index_range: Some(Range(
start: 219,
end: 771,
@ -370,15 +393,17 @@ VideoPlayer(
mime: "video/webm; codecs=\"vp9\"",
format: webm,
codec: vp9,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=22365208&dur=163.046&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=398&keepalive=yes&lmt=1608048380553749&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRgIhAI-VhcBU6o8LGmeuVYC2_zbxeGvC6XWf7yIOQ1RvjURhAiEA0YcZlVOI2ZUtKl-31__Hzax2SOUPeekCRjqjfw4m15s%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1",
url: Some("https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=22365208&dur=163.046&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=398&keepalive=yes&lmt=1608048380553749&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRgIhAI-VhcBU6o8LGmeuVYC2_zbxeGvC6XWf7yIOQ1RvjURhAiEA0YcZlVOI2ZUtKl-31__Hzax2SOUPeekCRjqjfw4m15s%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1"),
itag: 398,
bitrate: 1348419,
average_bitrate: 1097369,
size: Some(22365208),
last_modified: Some(1608048380553749),
index_range: Some(Range(
start: 700,
end: 1115,
@ -396,15 +421,17 @@ VideoPlayer(
mime: "video/mp4; codecs=\"av01.0.08M.08\"",
format: mp4,
codec: av01,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=65400181&dur=163.046&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=299&keepalive=yes&lmt=1580005649161486&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRgIhAIdbG-deTvLhp7mD2b-QZYQamPFv75l1bNBEEOMihrxPAiEA1NYvRlFphbRRvFIBCP-Ij9-5q8OTwUskgsL6LyIrD7c%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=2211222&vprv=1",
url: Some("https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=65400181&dur=163.046&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=299&keepalive=yes&lmt=1580005649161486&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRgIhAIdbG-deTvLhp7mD2b-QZYQamPFv75l1bNBEEOMihrxPAiEA1NYvRlFphbRRvFIBCP-Ij9-5q8OTwUskgsL6LyIrD7c%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=2211222&vprv=1"),
itag: 299,
bitrate: 4190323,
average_bitrate: 3208919,
size: Some(65400181),
last_modified: Some(1580005649161486),
index_range: Some(Range(
start: 740,
end: 1155,
@ -422,15 +449,17 @@ VideoPlayer(
mime: "video/mp4; codecs=\"avc1.64002a\"",
format: mp4,
codec: avc1,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=62993617&dur=163.046&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=303&keepalive=yes&lmt=1608509371758331&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAJ8n34LQhg6iEg1Ux9rDkk48e8l3vBR4WwuHeIpKnorlAiBopK4z-nq-pJTPTmrdbbKPW1Lfufdz2f9sGUKY-dzk5A%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1",
url: Some("https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=62993617&dur=163.046&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=303&keepalive=yes&lmt=1608509371758331&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAJ8n34LQhg6iEg1Ux9rDkk48e8l3vBR4WwuHeIpKnorlAiBopK4z-nq-pJTPTmrdbbKPW1Lfufdz2f9sGUKY-dzk5A%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1"),
itag: 303,
bitrate: 3832648,
average_bitrate: 3090839,
size: Some(62993617),
last_modified: Some(1608509371758331),
index_range: Some(Range(
start: 219,
end: 776,
@ -448,15 +477,17 @@ VideoPlayer(
mime: "video/webm; codecs=\"vp9\"",
format: webm,
codec: vp9,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=42567727&dur=163.046&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=399&keepalive=yes&lmt=1608052932785283&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAMewAT3SgJRGn7wqDaDzNWcsAfrjFRu6k0wm7O_5YJeQAiANVhGmILp_gmNXnmixDesxsZ44_72YBT2SqjLLSZV32w%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1",
url: Some("https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C394%2C395%2C396%2C397%2C398%2C399&c=WEB&clen=42567727&dur=163.046&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=399&keepalive=yes&lmt=1608052932785283&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAMewAT3SgJRGn7wqDaDzNWcsAfrjFRu6k0wm7O_5YJeQAiANVhGmILp_gmNXnmixDesxsZ44_72YBT2SqjLLSZV32w%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1"),
itag: 399,
bitrate: 2572342,
average_bitrate: 2088624,
size: Some(42567727),
last_modified: Some(1608052932785283),
index_range: Some(Range(
start: 700,
end: 1115,
@ -474,17 +505,19 @@ VideoPlayer(
mime: "video/mp4; codecs=\"av01.0.09M.08\"",
format: mp4,
codec: av01,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
],
audio_streams: [
AudioStream(
url: "https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?c=WEB&clen=934449&dur=163.061&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=249&keepalive=yes&lmt=1608509101590706&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fwebm&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAOdVu1woKNveQspV4WPm1PHrOBuzlrnPu2ZLvyiYZCSbAiAYODN_y5t1oU334SHUqqgyc4Wnt-9If-W98Fd966fy2A%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1",
url: Some("https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?c=WEB&clen=934449&dur=163.061&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=249&keepalive=yes&lmt=1608509101590706&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fwebm&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAOdVu1woKNveQspV4WPm1PHrOBuzlrnPu2ZLvyiYZCSbAiAYODN_y5t1oU334SHUqqgyc4Wnt-9If-W98Fd966fy2A%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1"),
itag: 249,
bitrate: 53039,
average_bitrate: 45845,
size: 934449,
last_modified: Some(1608509101590706),
index_range: Some(Range(
start: 266,
end: 551,
@ -500,15 +533,17 @@ VideoPlayer(
channels: Some(2),
loudness_db: Some(5.2200003),
track: None,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
AudioStream(
url: "https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?c=WEB&clen=1245866&dur=163.061&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=250&keepalive=yes&lmt=1608509101111096&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fwebm&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAMJJ-uGQureE70LIxHjHP9hFxqcWwsSlxXX6EjGKmFfEAiAvQ98YKkqUrweNnBZOI7pXJk1kuU_1hSsQ0KeNU4CbyQ%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1",
url: Some("https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?c=WEB&clen=1245866&dur=163.061&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=250&keepalive=yes&lmt=1608509101111096&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fwebm&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAMJJ-uGQureE70LIxHjHP9hFxqcWwsSlxXX6EjGKmFfEAiAvQ98YKkqUrweNnBZOI7pXJk1kuU_1hSsQ0KeNU4CbyQ%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1"),
itag: 250,
bitrate: 71268,
average_bitrate: 61123,
size: 1245866,
last_modified: Some(1608509101111096),
index_range: Some(Range(
start: 266,
end: 551,
@ -524,15 +559,17 @@ VideoPlayer(
channels: Some(2),
loudness_db: Some(5.2200003),
track: None,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
AudioStream(
url: "https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?c=WEB&clen=2640283&dur=163.096&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=140&keepalive=yes&lmt=1580005579712232&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fmp4&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhANXIw4pIwIPvMGWnJSrA_bnmBX6KPBPqak18aPtKsI8jAiBvisRnEtFax7OTrwKbOiktCihoMraLkCi7Rnnu6JGmeQ%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=2211222&vprv=1",
url: Some("https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?c=WEB&clen=2640283&dur=163.096&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=140&keepalive=yes&lmt=1580005579712232&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fmp4&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhANXIw4pIwIPvMGWnJSrA_bnmBX6KPBPqak18aPtKsI8jAiBvisRnEtFax7OTrwKbOiktCihoMraLkCi7Rnnu6JGmeQ%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=2211222&vprv=1"),
itag: 140,
bitrate: 130268,
average_bitrate: 129508,
size: 2640283,
last_modified: Some(1580005579712232),
index_range: Some(Range(
start: 632,
end: 867,
@ -548,15 +585,17 @@ VideoPlayer(
channels: Some(2),
loudness_db: Some(5.2159004),
track: None,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
AudioStream(
url: "https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?c=WEB&clen=2476314&dur=163.061&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=251&keepalive=yes&lmt=1608509101894140&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fwebm&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAKP_KjT_SSnz5WGXaveO56pJAEw166qT3cpBdAZI1zwCAiBWZKVQZxfOPWnqSp5FnRDyQBQ-6a2nZopyA1eHicgHtw%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1",
url: Some("https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?c=WEB&clen=2476314&dur=163.061&ei=q1jpYtq3BJCX1gKVyJGQDg&expire=1659481355&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AGfBIFoT5D_NZAwXN7lVCS2VYLDMMegfaJQqvSJp-Hhy&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=251&keepalive=yes&lmt=1608509101894140&lsig=AG3C_xAwRgIhANxHzq0WC6OvdTpPJp52z3eGAm-jzUX7fcKiWlJ0T9kEAiEA02Bjesi_an2-pUh0kHdKQe0s_7micbcv3JKiBlxsYGs%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fwebm&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5edn6k&ms=au%2Conr&mt=1659459429&mv=m&mvi=4&n=T16m7p0RvV7UhQ&ns=tWuNfisHu8yiCA6Avm7nUlwH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAKP_KjT_SSnz5WGXaveO56pJAEw166qT3cpBdAZI1zwCAiBWZKVQZxfOPWnqSp5FnRDyQBQ-6a2nZopyA1eHicgHtw%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-KhrZGE2opztWyVdAtyUNlb8dXPDs&txp=1311222&vprv=1"),
itag: 251,
bitrate: 140633,
average_bitrate: 121491,
size: 2476314,
last_modified: Some(1608509101894140),
index_range: Some(Range(
start: 266,
end: 551,
@ -572,6 +611,7 @@ VideoPlayer(
channels: Some(2),
loudness_db: Some(5.2200003),
track: None,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
@ -588,6 +628,9 @@ VideoPlayer(
valid_until: "[date]",
hls_manifest_url: None,
dash_manifest_url: Some("https://manifest.googlevideo.com/api/manifest/dash/expire/1659481355/ei/q1jpYtq3BJCX1gKVyJGQDg/ip/2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e/id/a4fbddf14c6649b4/source/youtube/requiressl/yes/playback_host/rr4---sn-h0jelnez.googlevideo.com/mh/mQ/mm/31%2C26/mn/sn-h0jelnez%2Csn-4g5edn6k/ms/au%2Conr/mv/m/mvi/4/pl/37/hfr/all/as/fmp4_audio_clear%2Cwebm_audio_clear%2Cwebm2_audio_clear%2Cfmp4_sd_hd_clear%2Cwebm2_sd_hd_clear/initcwndbps/1513750/spc/lT-KhrZGE2opztWyVdAtyUNlb8dXPDs/vprv/1/mt/1659459429/fvip/4/keepalive/yes/fexp/24001373%2C24007246/itag/0/sparams/expire%2Cei%2Cip%2Cid%2Csource%2Crequiressl%2Chfr%2Cas%2Cspc%2Cvprv%2Citag/sig/AOq0QJ8wRgIhAPEjHK19PKVHqQeia6WF4qubuMYk74LGi8F8lk5ZMPkFAiEAsaB2pKQWBvuPnNUnbdQXHc-izgsHJUP793woC2xNJlg%3D/lsparams/playback_host%2Cmh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps/lsig/AG3C_xAwRQIgOY4xu4H9wqPVZ7vF2i0hFcOnqrur1XGoA43a7ZEuuSUCIQCyPxBKXUQrKFmknNEGpX5GSWySKgMw_xHBikWpKpKwvg%3D%3D"),
abr_streaming_url: None,
abr_ustreamer_config: None,
po_token: None,
preview_frames: [
Frameset(
url_template: "https://i.ytimg.com/sb/pPvd8UxmSbQ/storyboard3_L0/default.jpg?sqp=-oaymwENSDfyq4qpAwVwAcABBqLzl_8DBgjf8LPxBQ==&sigh=rs$AOn4CLAXobPyrylgm8IEvjlZzqYTiPe1Ow",

View file

@ -34,11 +34,12 @@ VideoPlayer(
),
video_streams: [
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=11439331&dur=163.096&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=18&lmt=1580005476071743&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=mAEwZepBJSQPkQ&ns=orl5qWACo00YlHHyQZ7a6awH&pl=37&ratebypass=yes&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhANfXubbDcXpc25m3F5xQ97ygJRjrTvm8ruVxgnxgFAUBAiAEnj_3KacDNTTLUk-6ZEbL-52zxmBLU1iuTEDx0NvJzA%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cratebypass%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=2211222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=11439331&dur=163.096&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=18&lmt=1580005476071743&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=mAEwZepBJSQPkQ&ns=orl5qWACo00YlHHyQZ7a6awH&pl=37&ratebypass=yes&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhANfXubbDcXpc25m3F5xQ97ygJRjrTvm8ruVxgnxgFAUBAiAEnj_3KacDNTTLUk-6ZEbL-52zxmBLU1iuTEDx0NvJzA%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cratebypass%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=2211222&vprv=1"),
itag: 18,
bitrate: 561339,
average_bitrate: 561109,
size: Some(11439331),
last_modified: Some(1580005476071743),
index_range: None,
init_range: None,
duration_ms: Some(163096),
@ -50,17 +51,19 @@ VideoPlayer(
mime: "video/mp4; codecs=\"avc1.42001E, mp4a.40.2\"",
format: mp4,
codec: avc1,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
],
video_only_streams: [
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=1484736&dur=163.029&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=278&keepalive=yes&lmt=1608509388295661&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=1taQMNHGExb_Vg&ns=UTT8RXHZNhPYTw6NgkzWMWEH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgEQ0-VVvo41T4l2X26p5zP8Wo8sXOPmBWvCf2OW33ilgCIH2bIFOYgpmsml7FvRQj_SoLzPh7yBvmLZ61Kgsj4FUe&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=1311222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=1484736&dur=163.029&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=278&keepalive=yes&lmt=1608509388295661&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=1taQMNHGExb_Vg&ns=UTT8RXHZNhPYTw6NgkzWMWEH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgEQ0-VVvo41T4l2X26p5zP8Wo8sXOPmBWvCf2OW33ilgCIH2bIFOYgpmsml7FvRQj_SoLzPh7yBvmLZ61Kgsj4FUe&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=1311222&vprv=1"),
itag: 278,
bitrate: 87458,
average_bitrate: 72857,
size: Some(1484736),
last_modified: Some(1608509388295661),
index_range: Some(Range(
start: 218,
end: 751,
@ -78,15 +81,17 @@ VideoPlayer(
mime: "video/webm; codecs=\"vp9\"",
format: webm,
codec: vp9,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=2973283&dur=163.029&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=242&keepalive=yes&lmt=1608509388282028&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=1taQMNHGExb_Vg&ns=UTT8RXHZNhPYTw6NgkzWMWEH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAO7DI5E91yHpLhgiWg9C99NsMoJBVOWsNTNF3os9kREQAiAr2oC8vFtXIHwkJJt45q0sdmjiJdkTO2i8VAjUodk6Xw%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=1311222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=2973283&dur=163.029&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=242&keepalive=yes&lmt=1608509388282028&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=1taQMNHGExb_Vg&ns=UTT8RXHZNhPYTw6NgkzWMWEH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAO7DI5E91yHpLhgiWg9C99NsMoJBVOWsNTNF3os9kREQAiAr2oC8vFtXIHwkJJt45q0sdmjiJdkTO2i8VAjUodk6Xw%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=1311222&vprv=1"),
itag: 242,
bitrate: 184064,
average_bitrate: 145902,
size: Some(2973283),
last_modified: Some(1608509388282028),
index_range: Some(Range(
start: 219,
end: 753,
@ -104,15 +109,17 @@ VideoPlayer(
mime: "video/webm; codecs=\"vp9\"",
format: webm,
codec: vp9,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=7808990&dur=163.029&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=134&keepalive=yes&lmt=1580005649163759&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=1taQMNHGExb_Vg&ns=UTT8RXHZNhPYTw6NgkzWMWEH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgTkOjFd0nExEtpr8sBIaNu9HhkxWNdjhSKufHMhLR8-8CIHJAmOuCD7VBv_krH6rn5zqXFqAfsq9rQPXlC3CcQrjM&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=2211222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=7808990&dur=163.029&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=134&keepalive=yes&lmt=1580005649163759&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=1taQMNHGExb_Vg&ns=UTT8RXHZNhPYTw6NgkzWMWEH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgTkOjFd0nExEtpr8sBIaNu9HhkxWNdjhSKufHMhLR8-8CIHJAmOuCD7VBv_krH6rn5zqXFqAfsq9rQPXlC3CcQrjM&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=2211222&vprv=1"),
itag: 134,
bitrate: 538143,
average_bitrate: 383195,
size: Some(7808990),
last_modified: Some(1580005649163759),
index_range: Some(Range(
start: 740,
end: 1155,
@ -130,15 +137,17 @@ VideoPlayer(
mime: "video/mp4; codecs=\"avc1.4d401e\"",
format: mp4,
codec: avc1,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=5169510&dur=163.029&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=243&keepalive=yes&lmt=1608509388282405&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=1taQMNHGExb_Vg&ns=UTT8RXHZNhPYTw6NgkzWMWEH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAPqQfxwIANgIC3DrQ6avaWOhCvIMLdzMPQtFOx2gwEXNAiAwJp2mgN9-zl4vPOB2uoQXOfmGsYDB470q1zg7wRW4Sw%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=1311222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=5169510&dur=163.029&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=243&keepalive=yes&lmt=1608509388282405&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=1taQMNHGExb_Vg&ns=UTT8RXHZNhPYTw6NgkzWMWEH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAPqQfxwIANgIC3DrQ6avaWOhCvIMLdzMPQtFOx2gwEXNAiAwJp2mgN9-zl4vPOB2uoQXOfmGsYDB470q1zg7wRW4Sw%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=1311222&vprv=1"),
itag: 243,
bitrate: 319085,
average_bitrate: 253673,
size: Some(5169510),
last_modified: Some(1608509388282405),
index_range: Some(Range(
start: 220,
end: 754,
@ -156,15 +165,17 @@ VideoPlayer(
mime: "video/webm; codecs=\"vp9\"",
format: webm,
codec: vp9,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=8890590&dur=163.029&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=244&keepalive=yes&lmt=1608509388284632&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=1taQMNHGExb_Vg&ns=UTT8RXHZNhPYTw6NgkzWMWEH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAIjdvhcThMxoo_v2bzEjaR_w0ryWFQDs0f0INaI5WPcVAiApQZUYTqcQJdfxZlNSsp7cl3FK8XPfDZ-qbVvj9GuauQ%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=1311222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=8890590&dur=163.029&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=244&keepalive=yes&lmt=1608509388284632&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=1taQMNHGExb_Vg&ns=UTT8RXHZNhPYTw6NgkzWMWEH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAIjdvhcThMxoo_v2bzEjaR_w0ryWFQDs0f0INaI5WPcVAiApQZUYTqcQJdfxZlNSsp7cl3FK8XPfDZ-qbVvj9GuauQ%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=1311222&vprv=1"),
itag: 244,
bitrate: 539056,
average_bitrate: 436270,
size: Some(8890590),
last_modified: Some(1608509388284632),
index_range: Some(Range(
start: 220,
end: 754,
@ -182,15 +193,17 @@ VideoPlayer(
mime: "video/webm; codecs=\"vp9\"",
format: webm,
codec: vp9,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=16547577&dur=163.029&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=247&keepalive=yes&lmt=1608509388326822&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=1taQMNHGExb_Vg&ns=UTT8RXHZNhPYTw6NgkzWMWEH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgBV4Oa1IQ0YNDvRrKO5ec3Pfbg65MxzmIxCcm0gOuwT0CIFysQdow6DQXzz1W9KZVuqACTdjXQ3-yiBj9GcmNw3HE&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=1311222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=16547577&dur=163.029&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=247&keepalive=yes&lmt=1608509388326822&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=1taQMNHGExb_Vg&ns=UTT8RXHZNhPYTw6NgkzWMWEH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgBV4Oa1IQ0YNDvRrKO5ec3Pfbg65MxzmIxCcm0gOuwT0CIFysQdow6DQXzz1W9KZVuqACTdjXQ3-yiBj9GcmNw3HE&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=1311222&vprv=1"),
itag: 247,
bitrate: 982813,
average_bitrate: 812006,
size: Some(16547577),
last_modified: Some(1608509388326822),
index_range: Some(Range(
start: 220,
end: 754,
@ -208,15 +221,17 @@ VideoPlayer(
mime: "video/webm; codecs=\"vp9\"",
format: webm,
codec: vp9,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=35955780&dur=163.046&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=302&keepalive=yes&lmt=1608509234088626&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=1taQMNHGExb_Vg&ns=UTT8RXHZNhPYTw6NgkzWMWEH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAOiqSNfGfOprZ9InWVMc7gY0KrTf8weLibcpK0W2Hfa6AiAFHW213qsByzlar5ivCAYttjo1rPciQnLEnh-izJ3ZhA%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=1311222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=35955780&dur=163.046&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=302&keepalive=yes&lmt=1608509234088626&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=1taQMNHGExb_Vg&ns=UTT8RXHZNhPYTw6NgkzWMWEH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAOiqSNfGfOprZ9InWVMc7gY0KrTf8weLibcpK0W2Hfa6AiAFHW213qsByzlar5ivCAYttjo1rPciQnLEnh-izJ3ZhA%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=1311222&vprv=1"),
itag: 302,
bitrate: 2354009,
average_bitrate: 1764202,
size: Some(35955780),
last_modified: Some(1608509234088626),
index_range: Some(Range(
start: 219,
end: 771,
@ -234,15 +249,17 @@ VideoPlayer(
mime: "video/webm; codecs=\"vp9\"",
format: webm,
codec: vp9,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=65400181&dur=163.046&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=299&keepalive=yes&lmt=1580005649161486&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=1taQMNHGExb_Vg&ns=UTT8RXHZNhPYTw6NgkzWMWEH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgdkJv6w9_Azf0m6poA-ULyX0eH_GKBtSJRwUY1lNBAZgCIDCrC0lnu__ycTaIhg0pUcsRUqay60S3QMo5084EWifd&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=2211222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=65400181&dur=163.046&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=299&keepalive=yes&lmt=1580005649161486&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=1taQMNHGExb_Vg&ns=UTT8RXHZNhPYTw6NgkzWMWEH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgdkJv6w9_Azf0m6poA-ULyX0eH_GKBtSJRwUY1lNBAZgCIDCrC0lnu__ycTaIhg0pUcsRUqay60S3QMo5084EWifd&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=2211222&vprv=1"),
itag: 299,
bitrate: 4190323,
average_bitrate: 3208919,
size: Some(65400181),
last_modified: Some(1580005649161486),
index_range: Some(Range(
start: 740,
end: 1155,
@ -260,15 +277,17 @@ VideoPlayer(
mime: "video/mp4; codecs=\"avc1.64002a\"",
format: mp4,
codec: avc1,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=62993617&dur=163.046&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=303&keepalive=yes&lmt=1608509371758331&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=1taQMNHGExb_Vg&ns=UTT8RXHZNhPYTw6NgkzWMWEH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgZi9dDSMWh10NID8-QNn3azIH1zw5UooZrRTPZjVn7hYCIAm9bFc6NBwJ_DzY4V2R_zGmJSpOwQl8LEsfCb7hf6i7&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=1311222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=62993617&dur=163.046&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=303&keepalive=yes&lmt=1608509371758331&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=1taQMNHGExb_Vg&ns=UTT8RXHZNhPYTw6NgkzWMWEH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgZi9dDSMWh10NID8-QNn3azIH1zw5UooZrRTPZjVn7hYCIAm9bFc6NBwJ_DzY4V2R_zGmJSpOwQl8LEsfCb7hf6i7&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=1311222&vprv=1"),
itag: 303,
bitrate: 3832648,
average_bitrate: 3090839,
size: Some(62993617),
last_modified: Some(1608509371758331),
index_range: Some(Range(
start: 219,
end: 776,
@ -286,17 +305,19 @@ VideoPlayer(
mime: "video/webm; codecs=\"vp9\"",
format: webm,
codec: vp9,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
],
audio_streams: [
AudioStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=934449&dur=163.061&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=249&keepalive=yes&lmt=1608509101590706&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=1taQMNHGExb_Vg&ns=UTT8RXHZNhPYTw6NgkzWMWEH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRgIhAMUfr2X-eQJt1abn-IK1H4d5DtvKZuBaETo4opNi6mqCAiEAvBmrmuaoFjB1CJ2Kug87w-Uv6OCdxyrJ_3HIaHX9KBI%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=1311222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=934449&dur=163.061&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=249&keepalive=yes&lmt=1608509101590706&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=1taQMNHGExb_Vg&ns=UTT8RXHZNhPYTw6NgkzWMWEH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRgIhAMUfr2X-eQJt1abn-IK1H4d5DtvKZuBaETo4opNi6mqCAiEAvBmrmuaoFjB1CJ2Kug87w-Uv6OCdxyrJ_3HIaHX9KBI%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=1311222&vprv=1"),
itag: 249,
bitrate: 53039,
average_bitrate: 45845,
size: 934449,
last_modified: Some(1608509101590706),
index_range: Some(Range(
start: 266,
end: 551,
@ -312,15 +333,17 @@ VideoPlayer(
channels: Some(2),
loudness_db: Some(0.0006532669),
track: None,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
AudioStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=1245866&dur=163.061&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=250&keepalive=yes&lmt=1608509101111096&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=1taQMNHGExb_Vg&ns=UTT8RXHZNhPYTw6NgkzWMWEH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAIflUU4t4Ukf4CXW3ttB5c8SnP4z4z3ef-7EFVMFv4U8AiAlcKmmofCTzzr2NRRsRVosQdpDBphUqWyVzS7noGrixw%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=1311222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=1245866&dur=163.061&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=250&keepalive=yes&lmt=1608509101111096&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=1taQMNHGExb_Vg&ns=UTT8RXHZNhPYTw6NgkzWMWEH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAIflUU4t4Ukf4CXW3ttB5c8SnP4z4z3ef-7EFVMFv4U8AiAlcKmmofCTzzr2NRRsRVosQdpDBphUqWyVzS7noGrixw%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=1311222&vprv=1"),
itag: 250,
bitrate: 71268,
average_bitrate: 61123,
size: 1245866,
last_modified: Some(1608509101111096),
index_range: Some(Range(
start: 266,
end: 551,
@ -336,15 +359,17 @@ VideoPlayer(
channels: Some(2),
loudness_db: Some(0.0006532669),
track: None,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
AudioStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=2640283&dur=163.096&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=140&keepalive=yes&lmt=1580005579712232&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=1taQMNHGExb_Vg&ns=UTT8RXHZNhPYTw6NgkzWMWEH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRgIhALBveArAZ7DP9r1BIpNz6ZXst5MtzvUM72jhtYMrildCAiEArvwqaqcowZwR_UrRM-O7jq2CMxgBtbnmul27AEcBqEI%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=2211222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=2640283&dur=163.096&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=140&keepalive=yes&lmt=1580005579712232&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=1taQMNHGExb_Vg&ns=UTT8RXHZNhPYTw6NgkzWMWEH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRgIhALBveArAZ7DP9r1BIpNz6ZXst5MtzvUM72jhtYMrildCAiEArvwqaqcowZwR_UrRM-O7jq2CMxgBtbnmul27AEcBqEI%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=2211222&vprv=1"),
itag: 140,
bitrate: 130268,
average_bitrate: 129508,
size: 2640283,
last_modified: Some(1580005579712232),
index_range: Some(Range(
start: 632,
end: 867,
@ -360,15 +385,17 @@ VideoPlayer(
channels: Some(2),
loudness_db: Some(-0.003446579),
track: None,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
AudioStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=2476314&dur=163.061&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=251&keepalive=yes&lmt=1608509101894140&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=1taQMNHGExb_Vg&ns=UTT8RXHZNhPYTw6NgkzWMWEH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgARPqD-6172OshMHeV8DpONV7tnPvdsxcg8QlaIGxcuMCICSe8LWvhRTEO2bdAQ43OzOoc5XfJcj3veyhScVXVz8O&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=1311222&vprv=1",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=WEB_REMIX&clen=2476314&dur=163.061&ei=knDpYub6BojEgAf6jbLgDw&expire=1659487474&fexp=24001373%2C24007246&fvip=4&gir=yes&id=o-AM-wcJVO-yYYbVFnuifnzM4eRnD-AG1bS1AhLoDqi_is&initcwndbps=1418750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=251&keepalive=yes&lmt=1608509101894140&lsig=AG3C_xAwRgIhAMwYJqxve8BSujC-oaSFBbq67p-rFi7saU5V8Yb3qrjLAiEAlrMKR_sadHrkFpy7o7lGzKOCmU1OQazCNBbXjDT2a-o%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1659465669&mv=m&mvi=5&n=1taQMNHGExb_Vg&ns=UTT8RXHZNhPYTw6NgkzWMWEH&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgARPqD-6172OshMHeV8DpONV7tnPvdsxcg8QlaIGxcuMCICSe8LWvhRTEO2bdAQ43OzOoc5XfJcj3veyhScVXVz8O&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&spc=lT-Khox4YuJQ2wmH79zYALRvsWTPCUc&txp=1311222&vprv=1"),
itag: 251,
bitrate: 140633,
average_bitrate: 121491,
size: 2476314,
last_modified: Some(1608509101894140),
index_range: Some(Range(
start: 266,
end: 551,
@ -384,6 +411,7 @@ VideoPlayer(
channels: Some(2),
loudness_db: Some(0.0006532669),
track: None,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
@ -400,6 +428,9 @@ VideoPlayer(
valid_until: "[date]",
hls_manifest_url: None,
dash_manifest_url: Some("https://manifest.googlevideo.com/api/manifest/dash/expire/1659487474/ei/knDpYub6BojEgAf6jbLgDw/ip/2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e/id/a4fbddf14c6649b4/source/youtube/requiressl/yes/playback_host/rr5---sn-h0jeenek.googlevideo.com/mh/mQ/mm/31%2C29/mn/sn-h0jeenek%2Csn-h0jelnez/ms/au%2Crdu/mv/m/mvi/5/pl/37/hfr/all/as/fmp4_audio_clear%2Cwebm_audio_clear%2Cwebm2_audio_clear%2Cfmp4_sd_hd_clear%2Cwebm2_sd_hd_clear/initcwndbps/1418750/spc/lT-Khox4YuJQ2wmH79zYALRvsWTPCUc/vprv/1/mt/1659465669/fvip/4/keepalive/yes/fexp/24001373%2C24007246/itag/0/sparams/expire%2Cei%2Cip%2Cid%2Csource%2Crequiressl%2Chfr%2Cas%2Cspc%2Cvprv%2Citag/sig/AOq0QJ8wRAIgErABhAEaoKHUDu9dDbpxE_8gR4b8WWAi61fnu8UKnuICIEYrEKcHvqHdO4V3R7cvSGwi_HGH34IlQsKbziOfMBov/lsparams/playback_host%2Cmh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps/lsig/AG3C_xAwRQIgJxHmH0Sxo3cY_pW_ZzQ3hW9-7oz6K_pZWcUdrDDQ2sQCIQDJYNINQwLgKelgbO3CZYx7sMxdUAFpWdokmRBQ77vwvw%3D%3D"),
abr_streaming_url: None,
abr_ustreamer_config: None,
po_token: None,
preview_frames: [
Frameset(
url_template: "https://i.ytimg.com/sb/pPvd8UxmSbQ/storyboard3_L0/default.jpg?sqp=-oaymwENSDfyq4qpAwVwAcABBqLzl_8DBgjf8LPxBQ==&sigh=rs$AOn4CLAXobPyrylgm8IEvjlZzqYTiPe1Ow",

View file

@ -57,11 +57,12 @@ VideoPlayer(
video_streams: [],
video_only_streams: [
VideoStream(
url: "https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?c=IOS&clen=7808990&dur=163.029&ei=q1jpYq-xHs7NgQev0bfwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=5&gir=yes&id=o-ANNg3iPHI56jhLSlPQk4pi4mdub5iAby0hmJBVrtiJgY&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=134&keepalive=yes&lmt=1580005649163759&lsig=AG3C_xAwRQIgWKVoDpyI6QmVnkdGzdirFtjMAXhmLex64VTO7UUJd-4CIQDoJKkT2-Kpa7j0merJJoZDs4IkkXSjdNm3bvdCL8t2Pg%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jelnez%2Csn-h0jeenek&ms=au%2Crdu&mt=1659459429&mv=m&mvi=4&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRgIhAMFQo_RyC3Ud44QVGtKckMcuq5UQ3J7CgYsYl0bXaWMUAiEAhMi1h0ru4zoIGX0jBZT23dozvtrhf_m61p4qbAfEhZo%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Csvpuc%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&svpuc=1&txp=2211222&vprv=1",
url: Some("https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?c=IOS&clen=7808990&dur=163.029&ei=q1jpYq-xHs7NgQev0bfwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=5&gir=yes&id=o-ANNg3iPHI56jhLSlPQk4pi4mdub5iAby0hmJBVrtiJgY&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=134&keepalive=yes&lmt=1580005649163759&lsig=AG3C_xAwRQIgWKVoDpyI6QmVnkdGzdirFtjMAXhmLex64VTO7UUJd-4CIQDoJKkT2-Kpa7j0merJJoZDs4IkkXSjdNm3bvdCL8t2Pg%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jelnez%2Csn-h0jeenek&ms=au%2Crdu&mt=1659459429&mv=m&mvi=4&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRgIhAMFQo_RyC3Ud44QVGtKckMcuq5UQ3J7CgYsYl0bXaWMUAiEAhMi1h0ru4zoIGX0jBZT23dozvtrhf_m61p4qbAfEhZo%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Csvpuc%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&svpuc=1&txp=2211222&vprv=1"),
itag: 134,
bitrate: 538143,
average_bitrate: 383195,
size: Some(7808990),
last_modified: Some(1580005649163759),
index_range: Some(Range(
start: 740,
end: 1155,
@ -79,15 +80,17 @@ VideoPlayer(
mime: "video/mp4; codecs=\"avc1.4D401E\"",
format: mp4,
codec: avc1,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?c=IOS&clen=65400181&dur=163.046&ei=q1jpYq-xHs7NgQev0bfwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=5&gir=yes&id=o-ANNg3iPHI56jhLSlPQk4pi4mdub5iAby0hmJBVrtiJgY&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=299&keepalive=yes&lmt=1580005649161486&lsig=AG3C_xAwRQIgWKVoDpyI6QmVnkdGzdirFtjMAXhmLex64VTO7UUJd-4CIQDoJKkT2-Kpa7j0merJJoZDs4IkkXSjdNm3bvdCL8t2Pg%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jelnez%2Csn-h0jeenek&ms=au%2Crdu&mt=1659459429&mv=m&mvi=4&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRgIhAP6zxXXA18ToZWUfalauhhsgOsDHTu-R0QrqNrJR7D5kAiEAi8HBa9OkYwmA0bcRxhgvXfN9JsFlXwCWJ-x4ty6TjoY%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Csvpuc%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&svpuc=1&txp=2211222&vprv=1",
url: Some("https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?c=IOS&clen=65400181&dur=163.046&ei=q1jpYq-xHs7NgQev0bfwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=5&gir=yes&id=o-ANNg3iPHI56jhLSlPQk4pi4mdub5iAby0hmJBVrtiJgY&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=299&keepalive=yes&lmt=1580005649161486&lsig=AG3C_xAwRQIgWKVoDpyI6QmVnkdGzdirFtjMAXhmLex64VTO7UUJd-4CIQDoJKkT2-Kpa7j0merJJoZDs4IkkXSjdNm3bvdCL8t2Pg%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jelnez%2Csn-h0jeenek&ms=au%2Crdu&mt=1659459429&mv=m&mvi=4&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRgIhAP6zxXXA18ToZWUfalauhhsgOsDHTu-R0QrqNrJR7D5kAiEAi8HBa9OkYwmA0bcRxhgvXfN9JsFlXwCWJ-x4ty6TjoY%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Csvpuc%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&svpuc=1&txp=2211222&vprv=1"),
itag: 299,
bitrate: 4190323,
average_bitrate: 3208919,
size: Some(65400181),
last_modified: Some(1580005649161486),
index_range: Some(Range(
start: 740,
end: 1155,
@ -105,17 +108,19 @@ VideoPlayer(
mime: "video/mp4; codecs=\"avc1.64002A\"",
format: mp4,
codec: avc1,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
],
audio_streams: [
AudioStream(
url: "https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?c=IOS&clen=995840&dur=163.189&ei=q1jpYq-xHs7NgQev0bfwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=5&gir=yes&id=o-ANNg3iPHI56jhLSlPQk4pi4mdub5iAby0hmJBVrtiJgY&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=139&keepalive=yes&lmt=1580005582214385&lsig=AG3C_xAwRQIgWKVoDpyI6QmVnkdGzdirFtjMAXhmLex64VTO7UUJd-4CIQDoJKkT2-Kpa7j0merJJoZDs4IkkXSjdNm3bvdCL8t2Pg%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fmp4&mm=31%2C29&mn=sn-h0jelnez%2Csn-h0jeenek&ms=au%2Crdu&mt=1659459429&mv=m&mvi=4&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRgIhAMXDvZZznm1LafIKh_pdGf-TjY5Kz-F9N67o6gXenKouAiEA5qji45i5ABmAytPDOORjw0OaBmwX88S7bgUWcmF-_LU%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Csvpuc%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&svpuc=1&txp=2211222&vprv=1",
url: Some("https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?c=IOS&clen=995840&dur=163.189&ei=q1jpYq-xHs7NgQev0bfwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=5&gir=yes&id=o-ANNg3iPHI56jhLSlPQk4pi4mdub5iAby0hmJBVrtiJgY&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=139&keepalive=yes&lmt=1580005582214385&lsig=AG3C_xAwRQIgWKVoDpyI6QmVnkdGzdirFtjMAXhmLex64VTO7UUJd-4CIQDoJKkT2-Kpa7j0merJJoZDs4IkkXSjdNm3bvdCL8t2Pg%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fmp4&mm=31%2C29&mn=sn-h0jelnez%2Csn-h0jeenek&ms=au%2Crdu&mt=1659459429&mv=m&mvi=4&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRgIhAMXDvZZznm1LafIKh_pdGf-TjY5Kz-F9N67o6gXenKouAiEA5qji45i5ABmAytPDOORjw0OaBmwX88S7bgUWcmF-_LU%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Csvpuc%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&svpuc=1&txp=2211222&vprv=1"),
itag: 139,
bitrate: 49724,
average_bitrate: 48818,
size: 995840,
last_modified: Some(1580005582214385),
index_range: Some(Range(
start: 641,
end: 876,
@ -131,15 +136,17 @@ VideoPlayer(
channels: Some(2),
loudness_db: Some(5.2159004),
track: None,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
AudioStream(
url: "https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?c=IOS&clen=2640283&dur=163.096&ei=q1jpYq-xHs7NgQev0bfwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=5&gir=yes&id=o-ANNg3iPHI56jhLSlPQk4pi4mdub5iAby0hmJBVrtiJgY&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=140&keepalive=yes&lmt=1580005579712232&lsig=AG3C_xAwRQIgWKVoDpyI6QmVnkdGzdirFtjMAXhmLex64VTO7UUJd-4CIQDoJKkT2-Kpa7j0merJJoZDs4IkkXSjdNm3bvdCL8t2Pg%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fmp4&mm=31%2C29&mn=sn-h0jelnez%2Csn-h0jeenek&ms=au%2Crdu&mt=1659459429&mv=m&mvi=4&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIgcsPm6rrUAwCi1VTGf0FMDTjzjM01__iTC13PnzDTFeQCIQCJ_EGeKVZztkmK3Cr7gVuxUP83XCSlP01YLx5FO-PPcQ%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Csvpuc%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&svpuc=1&txp=2211222&vprv=1",
url: Some("https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?c=IOS&clen=2640283&dur=163.096&ei=q1jpYq-xHs7NgQev0bfwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=5&gir=yes&id=o-ANNg3iPHI56jhLSlPQk4pi4mdub5iAby0hmJBVrtiJgY&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&itag=140&keepalive=yes&lmt=1580005579712232&lsig=AG3C_xAwRQIgWKVoDpyI6QmVnkdGzdirFtjMAXhmLex64VTO7UUJd-4CIQDoJKkT2-Kpa7j0merJJoZDs4IkkXSjdNm3bvdCL8t2Pg%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fmp4&mm=31%2C29&mn=sn-h0jelnez%2Csn-h0jeenek&ms=au%2Crdu&mt=1659459429&mv=m&mvi=4&otfp=1&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIgcsPm6rrUAwCi1VTGf0FMDTjzjM01__iTC13PnzDTFeQCIQCJ_EGeKVZztkmK3Cr7gVuxUP83XCSlP01YLx5FO-PPcQ%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Csvpuc%2Cmime%2Cgir%2Cclen%2Cotfp%2Cdur%2Clmt&svpuc=1&txp=2211222&vprv=1"),
itag: 140,
bitrate: 130268,
average_bitrate: 129508,
size: 2640283,
last_modified: Some(1580005579712232),
index_range: Some(Range(
start: 632,
end: 867,
@ -155,6 +162,7 @@ VideoPlayer(
channels: Some(2),
loudness_db: Some(5.2159004),
track: None,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
@ -171,6 +179,9 @@ VideoPlayer(
valid_until: "[date]",
hls_manifest_url: Some("https://manifest.googlevideo.com/api/manifest/hls_variant/expire/1659481355/ei/q1jpYq-xHs7NgQev0bfwAQ/ip/2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e/id/a4fbddf14c6649b4/source/youtube/requiressl/yes/playback_host/rr4---sn-h0jelnez.googlevideo.com/mh/mQ/mm/31%2C29/mn/sn-h0jelnez%2Csn-h0jeenek/ms/au%2Crdu/mv/m/mvi/4/pl/37/hfr/1/demuxed/1/tts_caps/1/maudio/1/initcwndbps/1513750/vprv/1/go/1/mt/1659459429/fvip/5/nvgoi/1/short_key/1/ncsapi/1/keepalive/yes/fexp/24001373%2C24007246/dover/13/itag/0/playlist_type/DVR/sparams/expire%2Cei%2Cip%2Cid%2Csource%2Crequiressl%2Chfr%2Cdemuxed%2Ctts_caps%2Cmaudio%2Cvprv%2Cgo%2Citag%2Cplaylist_type/sig/AOq0QJ8wRQIhAIYnEHvIgJtJ8hehAXNtVY3qsgsq_GdOhWf2hkJZe6lCAiBxaRY_nubYp6hBizcAg_KFkKnkG-t2XYLRQ5wGdM3AjA%3D%3D/lsparams/playback_host%2Cmh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps/lsig/AG3C_xAwRgIhAM_91Kk_0VLuSsR6nLCY7LdtWojyRAzXSScd_X9ShRROAiEA1AF4VY04F71NsAI8_j3iqjuXnWL9s6NoXHq7P8-bHx8%3D/file/index.m3u8"),
dash_manifest_url: None,
abr_streaming_url: Some("https://rr4---sn-h0jelnez.googlevideo.com/videoplayback?c=IOS&ei=q1jpYq-xHs7NgQev0bfwAQ&expire=1659481355&fexp=24001373%2C24007246&fvip=5&id=o-ANNg3iPHI56jhLSlPQk4pi4mdub5iAby0hmJBVrtiJgY&initcwndbps=1513750&ip=2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e&keepalive=yes&lsig=AG3C_xAwRgIhAK6T5ehnFBsc0FOurPHH1ME_vGcVysI-g5jrtEsvX64sAiEArY-iAvQCsc4R8yg8dvMdpnuHPIcPMCnRgyh8E527HF0%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mm=31%2C29&mn=sn-h0jelnez%2Csn-h0jeenek&ms=au%2Crdu&mt=1659459429&mv=m&mvi=4&pl=37&rbqsm=fr&requiressl=yes&sabr=1&sig=AOq0QJ8wRgIhAJCJpb5gE12jQc2qOUy-Y61vEHeiAP_F78weNCzj8VklAiEAwR2PK52CmwsVHfRVk75OOYOxwYKNW2g1eDBw3COBP9w%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Csource%2Crequiressl%2Csvpuc%2Csabr&svpuc=1"),
abr_ustreamer_config: Some("CpYECp0DCAAQgAUY6AIl-n6qPi0AAIA_NT0Klz9oAXI6ChJtZnMyX2NtZnNfdjNfMV8wNDMSIAoeCgxkZXZpY2VfbW9kZWwSDgoMCgppcGhvbmUxNCw1GAAgAXIWChJtZnMyX2NtZnNfdjNfMV8wNDMYAHjoAoABAagBAbUB9ijcP6AC6AK4AgDaAmEQsOoBGKhGIKCcASjYNjCYdXCIJ4AB9AO4AQHgAQP4AQGQAgGYAgygAgGoAgGwAgG4AgHAAgHIAgHQAgLgAgHoAgLwAgGAAwKIA4gnmAMBqAMIwAMByAMB2AMB-LWR5QwB-gIvCAwQGBgyIDItAABwQjUAAIxCQAFIAVAKWAplAACAQGjAcM0BAACAP_ABAegC0AWCAwCQAwGgAwGoAwGwAwPQAwHYAwHgA5BOuAQBygRWChUI4NQDEJh1GOgHJQAAAAAoADAAQAEQ4NQDGNAPKjYKCnRiX2Nvc3RfNTAgDykAAAAAAAAAAEgBUAFdZmbmPmXNzEw-bZqZGT91mpkZP3jAqQeSAQDoBAHwBAH4BAGQBQGgBQEYASABMgwIqwIQjrrY25ug5wIyDAiqAhCOutjbm6DnAjIMCIcBEI662NuboOcCMgwIhgEQ78vY25ug5wIyDAiFARCOutjbm6DnAjIMCKABEI662NuboOcCMgwIiwEQ8aniu5ug5wIyDAiMARDozcm6m6DnAjoAEkwA8tjvwTBFAiAyo6mtW-M8Y8noBO_6Y3RmNo5U2LntCwiKcI2yye_zRgIhAK27d1UbqzhtkQhminIBAajD70ONeAQNwjt7VRpJRDimGgJlaQ=="),
po_token: None,
preview_frames: [
Frameset(
url_template: "https://i.ytimg.com/sb/pPvd8UxmSbQ/storyboard3_L0/default.jpg?sqp=-oaymwGbA0g48quKqQOSA4gBAZUBAAAEQpgBMqABPKgBBLABELABDbABDLABELABFbABH7ABJrABLbABDrABDrABD7ABErABF7ABK7ABLLABKbABD7ABDrABELABFbABH7ABKrABMrABKbABD7ABEbABFLABGLABJrABPbABOLABLbABEbABFLABHrABKrABMbABS7ABR7ABNrABFbABHLABKbABLrABObABR7ABTbABP7ABJbABLrABN7ABPbABR7ABUrABUbABRbABM7ABQLABQrABQ7ABTLABRLABRrABQ7gBEbgBEbgBFbgBI7gBRLgBQ7gBQ7gBQ7gBEbgBE7gBFrgBL7gBQ7gBQ7gBQ7gBQ7gBFbgBFrgBKbgBQ7gBQ7gBQ7gBQ7gBQ7gBI7gBL7gBQ7gBQ7gBQ7gBQ7gBQ7gBQ7gBRLgBQ7gBQ7gBQ7gBQ7gBQ7gBQrgBQrgBQ7gBQ7gBQ7gBQ7gBQ7gBQrgBQrgBQrgBQ7gBQ7gBQ7gBQ7gBQrgBQrgBQrgBQrgBQ7gBQ7gBQ7gBQrgBQrgBQrgBQrgBQqLzl_8DBgjf8LPxBQ==&sigh=rs$AOn4CLCsCT8Lprh2S0ptmCRsWH7VtDl3YQ",

View file

@ -24,11 +24,12 @@ VideoPlayer(
),
video_streams: [
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?bui=AXc671IT4iUCpJNJWUitTMgIi6njuKSsi3MNed1Szyf0qysTX0v1Nf6AyCvjIGbek5Fn50kuBrGtRJ5q&c=TVHTML5&clen=10262148&dur=163.096&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=18&lmt=1700885551970466&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=BMzwItzIOB1HhmG&ns=YmgbZhlLp0C-9ilsQWGAyUAQ&pl=26&ratebypass=yes&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRAIgUah4qH8RqPzmo75ExCWSiRYlUlsAk0v9gl638LitVNICICxFs5lK3CsmOAja0bsXavXkyykzpdhHZKGXOZQYT1f8&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cratebypass%2Cdur%2Clmt&svpuc=1&txp=1318224&vprv=1&xpc=EgVo2aDSNQ%3D%3D",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?bui=AXc671IT4iUCpJNJWUitTMgIi6njuKSsi3MNed1Szyf0qysTX0v1Nf6AyCvjIGbek5Fn50kuBrGtRJ5q&c=TVHTML5&clen=10262148&dur=163.096&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=18&lmt=1700885551970466&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=BMzwItzIOB1HhmG&ns=YmgbZhlLp0C-9ilsQWGAyUAQ&pl=26&ratebypass=yes&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRAIgUah4qH8RqPzmo75ExCWSiRYlUlsAk0v9gl638LitVNICICxFs5lK3CsmOAja0bsXavXkyykzpdhHZKGXOZQYT1f8&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cratebypass%2Cdur%2Clmt&svpuc=1&txp=1318224&vprv=1&xpc=EgVo2aDSNQ%3D%3D"),
itag: 18,
bitrate: 503574,
average_bitrate: 503367,
size: Some(10262148),
last_modified: Some(1700885551970466),
index_range: None,
init_range: None,
duration_ms: Some(163096),
@ -40,17 +41,19 @@ VideoPlayer(
mime: "video/mp4; codecs=\"avc1.42001E, mp4a.40.2\"",
format: mp4,
codec: avc1,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
],
video_only_streams: [
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=2273274&dur=163.029&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=160&keepalive=yes&lmt=1705967288821438&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRAIgb8eXnQ6MSJ3PuvFVBdYIWTnFobH8mTC9zbZpBNxLbBYCICkPLKEm3gNbW5HIFXs7bwF5rSqUKHHnXNK91qMslQog&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=1308224&vprv=1&xpc=EgVo2aDSNQ%3D%3D",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=2273274&dur=163.029&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=160&keepalive=yes&lmt=1705967288821438&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRAIgb8eXnQ6MSJ3PuvFVBdYIWTnFobH8mTC9zbZpBNxLbBYCICkPLKEm3gNbW5HIFXs7bwF5rSqUKHHnXNK91qMslQog&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=1308224&vprv=1&xpc=EgVo2aDSNQ%3D%3D"),
itag: 160,
bitrate: 114816,
average_bitrate: 111551,
size: Some(2273274),
last_modified: Some(1705967288821438),
index_range: Some(Range(
start: 738,
end: 1165,
@ -68,15 +71,17 @@ VideoPlayer(
mime: "video/mp4; codecs=\"avc1.4d400c\"",
format: mp4,
codec: avc1,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=1151892&dur=163.029&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=278&keepalive=yes&lmt=1705966620402771&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRQIhAP4IybR7cZRpx7IX1ke6UIu_hdFZN3LOuHBDywg_xv5WAiB8_XEx8VhT9OlFxmM-cY0fl6-7GT9uj3clMIPDk2w7cA%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=130F224&vprv=1&xpc=EgVo2aDSNQ%3D%3D",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=1151892&dur=163.029&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=278&keepalive=yes&lmt=1705966620402771&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRQIhAP4IybR7cZRpx7IX1ke6UIu_hdFZN3LOuHBDywg_xv5WAiB8_XEx8VhT9OlFxmM-cY0fl6-7GT9uj3clMIPDk2w7cA%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=130F224&vprv=1&xpc=EgVo2aDSNQ%3D%3D"),
itag: 278,
bitrate: 70630,
average_bitrate: 56524,
size: Some(1151892),
last_modified: Some(1705966620402771),
index_range: Some(Range(
start: 218,
end: 767,
@ -94,15 +99,17 @@ VideoPlayer(
mime: "video/webm; codecs=\"vp9\"",
format: webm,
codec: vp9,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=5026513&dur=163.029&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=133&keepalive=yes&lmt=1705967298859029&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRAIgPF0ms4OEe15BTjOFVCkvf52UeTUf0b62_pavCfEyGjcCIH-0AoxzyT8iioWFFaX7iYjqzzaUTpo8rgAPQ0uX8DJa&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=1308224&vprv=1&xpc=EgVo2aDSNQ%3D%3D",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=5026513&dur=163.029&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=133&keepalive=yes&lmt=1705967298859029&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRAIgPF0ms4OEe15BTjOFVCkvf52UeTUf0b62_pavCfEyGjcCIH-0AoxzyT8iioWFFaX7iYjqzzaUTpo8rgAPQ0uX8DJa&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=1308224&vprv=1&xpc=EgVo2aDSNQ%3D%3D"),
itag: 133,
bitrate: 257417,
average_bitrate: 246656,
size: Some(5026513),
last_modified: Some(1705967298859029),
index_range: Some(Range(
start: 739,
end: 1166,
@ -120,15 +127,17 @@ VideoPlayer(
mime: "video/mp4; codecs=\"avc1.4d4015\"",
format: mp4,
codec: avc1,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=2541351&dur=163.029&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=242&keepalive=yes&lmt=1705966614837727&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRQIgKj1JyMGwYtf16zLJsmbnizz5_v3jaZSa7-j-ls8-qzECIQDKUd50iIc52h7zOX50Hf1SkbV9h-hP4QHs-wkik1fk6Q%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=130F224&vprv=1&xpc=EgVo2aDSNQ%3D%3D",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=2541351&dur=163.029&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=242&keepalive=yes&lmt=1705966614837727&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRQIgKj1JyMGwYtf16zLJsmbnizz5_v3jaZSa7-j-ls8-qzECIQDKUd50iIc52h7zOX50Hf1SkbV9h-hP4QHs-wkik1fk6Q%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=130F224&vprv=1&xpc=EgVo2aDSNQ%3D%3D"),
itag: 242,
bitrate: 149589,
average_bitrate: 124706,
size: Some(2541351),
last_modified: Some(1705966614837727),
index_range: Some(Range(
start: 219,
end: 768,
@ -146,15 +155,17 @@ VideoPlayer(
mime: "video/webm; codecs=\"vp9\"",
format: webm,
codec: vp9,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=7810925&dur=163.029&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=134&keepalive=yes&lmt=1705967286812435&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRgIhAJ92IgZgdk3_WLsfzJV_ZyrSFSbzpsoJh3DkRKDHbNxzAiEA9UbnVlXQ2S3BUimLmWC5TZQfhIkc-PlLnZ81fL0S5yA%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=1308224&vprv=1&xpc=EgVo2aDSNQ%3D%3D",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=7810925&dur=163.029&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=134&keepalive=yes&lmt=1705967286812435&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRgIhAJ92IgZgdk3_WLsfzJV_ZyrSFSbzpsoJh3DkRKDHbNxzAiEA9UbnVlXQ2S3BUimLmWC5TZQfhIkc-PlLnZ81fL0S5yA%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=1308224&vprv=1&xpc=EgVo2aDSNQ%3D%3D"),
itag: 134,
bitrate: 537902,
average_bitrate: 383290,
size: Some(7810925),
last_modified: Some(1705967286812435),
index_range: Some(Range(
start: 740,
end: 1167,
@ -172,15 +183,17 @@ VideoPlayer(
mime: "video/mp4; codecs=\"avc1.4d401e\"",
format: mp4,
codec: avc1,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=4188954&dur=163.029&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=243&keepalive=yes&lmt=1705966624121874&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRQIgSCLGQvdZKNXym0zt7c3Yw_4e0J8-wNxtPagPRRn4dRoCIQCOj0IzalNG4EcowBIyK2LC6NLFDr8Zt6sNVkqPjw6lGg%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=130F224&vprv=1&xpc=EgVo2aDSNQ%3D%3D",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=4188954&dur=163.029&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=243&keepalive=yes&lmt=1705966624121874&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRQIgSCLGQvdZKNXym0zt7c3Yw_4e0J8-wNxtPagPRRn4dRoCIQCOj0IzalNG4EcowBIyK2LC6NLFDr8Zt6sNVkqPjw6lGg%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=130F224&vprv=1&xpc=EgVo2aDSNQ%3D%3D"),
itag: 243,
bitrate: 248858,
average_bitrate: 205556,
size: Some(4188954),
last_modified: Some(1705966624121874),
index_range: Some(Range(
start: 220,
end: 770,
@ -198,15 +211,17 @@ VideoPlayer(
mime: "video/webm; codecs=\"vp9\"",
format: webm,
codec: vp9,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=14723538&dur=163.029&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=135&keepalive=yes&lmt=1705967282545273&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRgIhAM843wAa1e7Gc1S69gfXckm7hdgIKPXp0bUSh3hO6W5zAiEA-DDEPGsZBmF5N8VbPy75dhy3rLpE1F18KtWgmrUm2Pg%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=1308224&vprv=1&xpc=EgVo2aDSNQ%3D%3D",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=14723538&dur=163.029&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=135&keepalive=yes&lmt=1705967282545273&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRgIhAM843wAa1e7Gc1S69gfXckm7hdgIKPXp0bUSh3hO6W5zAiEA-DDEPGsZBmF5N8VbPy75dhy3rLpE1F18KtWgmrUm2Pg%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=1308224&vprv=1&xpc=EgVo2aDSNQ%3D%3D"),
itag: 135,
bitrate: 978945,
average_bitrate: 722499,
size: Some(14723538),
last_modified: Some(1705967282545273),
index_range: Some(Range(
start: 740,
end: 1167,
@ -224,15 +239,17 @@ VideoPlayer(
mime: "video/mp4; codecs=\"avc1.4d401f\"",
format: mp4,
codec: avc1,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=7788899&dur=163.029&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=244&keepalive=yes&lmt=1705966622098793&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRQIhAKGyn799bfkVHYE195sPmD60dCMppqJrBM0O-sjgYTzzAiAoBjkNAtL90sXw2YP9UTW9JrMhPSvPiBI_KiCVMJAkFQ%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=130F224&vprv=1&xpc=EgVo2aDSNQ%3D%3D",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=7788899&dur=163.029&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=244&keepalive=yes&lmt=1705966622098793&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRQIhAKGyn799bfkVHYE195sPmD60dCMppqJrBM0O-sjgYTzzAiAoBjkNAtL90sXw2YP9UTW9JrMhPSvPiBI_KiCVMJAkFQ%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=130F224&vprv=1&xpc=EgVo2aDSNQ%3D%3D"),
itag: 244,
bitrate: 467884,
average_bitrate: 382209,
size: Some(7788899),
last_modified: Some(1705966622098793),
index_range: Some(Range(
start: 220,
end: 770,
@ -250,15 +267,17 @@ VideoPlayer(
mime: "video/webm; codecs=\"vp9\"",
format: webm,
codec: vp9,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=24616305&dur=163.029&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=136&keepalive=yes&lmt=1705967307531372&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRgIhAM57L2Utesn4xVyT0HSwR9Khv_S-efx4uFAbCPkZFoRXAiEAtIu63-jF2_FZkOMmZAqGU3SRU9QgxoajRjBhMFwcOuk%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=1308224&vprv=1&xpc=EgVo2aDSNQ%3D%3D",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=24616305&dur=163.029&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=136&keepalive=yes&lmt=1705967307531372&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRgIhAM57L2Utesn4xVyT0HSwR9Khv_S-efx4uFAbCPkZFoRXAiEAtIu63-jF2_FZkOMmZAqGU3SRU9QgxoajRjBhMFwcOuk%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=1308224&vprv=1&xpc=EgVo2aDSNQ%3D%3D"),
itag: 136,
bitrate: 1560439,
average_bitrate: 1207947,
size: Some(24616305),
last_modified: Some(1705967307531372),
index_range: Some(Range(
start: 739,
end: 1166,
@ -276,15 +295,17 @@ VideoPlayer(
mime: "video/mp4; codecs=\"avc1.4d401f\"",
format: mp4,
codec: avc1,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=14723992&dur=163.029&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=247&keepalive=yes&lmt=1705966613897741&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRgIhAL-upITxk7r9FQL5F4WL0A6SjPw673qyyzmXIC48eKfTAiEAlkdkx7IFYtehbhKakbffvIebpPXRtxSgBWLl7WEHCrE%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=130F224&vprv=1&xpc=EgVo2aDSNQ%3D%3D",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=14723992&dur=163.029&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=247&keepalive=yes&lmt=1705966613897741&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRgIhAL-upITxk7r9FQL5F4WL0A6SjPw673qyyzmXIC48eKfTAiEAlkdkx7IFYtehbhKakbffvIebpPXRtxSgBWLl7WEHCrE%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=130F224&vprv=1&xpc=EgVo2aDSNQ%3D%3D"),
itag: 247,
bitrate: 929607,
average_bitrate: 722521,
size: Some(14723992),
last_modified: Some(1705966613897741),
index_range: Some(Range(
start: 220,
end: 770,
@ -302,15 +323,17 @@ VideoPlayer(
mime: "video/webm; codecs=\"vp9\"",
format: webm,
codec: vp9,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=34544823&dur=163.046&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=298&keepalive=yes&lmt=1705967092637061&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRgIhAIIGU41JunuODw9qIlSoYQcwkCYO6k9XOVlDn1Nxqnu7AiEAoiMOgYU8s8lp01fW0L86hHrSrtlvOLSI9XA50iyIGBc%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=1308224&vprv=1&xpc=EgVo2aDSNQ%3D%3D",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=34544823&dur=163.046&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=298&keepalive=yes&lmt=1705967092637061&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRgIhAIIGU41JunuODw9qIlSoYQcwkCYO6k9XOVlDn1Nxqnu7AiEAoiMOgYU8s8lp01fW0L86hHrSrtlvOLSI9XA50iyIGBc%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=1308224&vprv=1&xpc=EgVo2aDSNQ%3D%3D"),
itag: 298,
bitrate: 2188961,
average_bitrate: 1694973,
size: Some(34544823),
last_modified: Some(1705967092637061),
index_range: Some(Range(
start: 739,
end: 1166,
@ -328,15 +351,17 @@ VideoPlayer(
mime: "video/mp4; codecs=\"avc1.4d4020\"",
format: mp4,
codec: avc1,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=30205331&dur=163.046&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=302&keepalive=yes&lmt=1705966545733919&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRQIhAL428Az_BKxxff4FlH4WleHSy4Igq3mR71NuTMOc9xU3AiBN4lXfH9DklGaQUMnOT8wAhiMuzR73bW3cwr744TSoNA%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=130F224&vprv=1&xpc=EgVo2aDSNQ%3D%3D",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=30205331&dur=163.046&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=302&keepalive=yes&lmt=1705966545733919&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRQIhAL428Az_BKxxff4FlH4WleHSy4Igq3mR71NuTMOc9xU3AiBN4lXfH9DklGaQUMnOT8wAhiMuzR73bW3cwr744TSoNA%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=130F224&vprv=1&xpc=EgVo2aDSNQ%3D%3D"),
itag: 302,
bitrate: 2250391,
average_bitrate: 1482051,
size: Some(30205331),
last_modified: Some(1705966545733919),
index_range: Some(Range(
start: 219,
end: 786,
@ -354,15 +379,17 @@ VideoPlayer(
mime: "video/webm; codecs=\"vp9\"",
format: webm,
codec: vp9,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=62057888&dur=163.046&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=299&keepalive=yes&lmt=1705967093743693&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRQIgBEemc0Cvd3KhNooNRblgX64_fjNSP30RmWDfFwDR7qYCIQCXpQ9FO0_X93ZHcyvRZCKX5gbJuusCReaRcJbRLFsM_g%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=1308224&vprv=1&xpc=EgVo2aDSNQ%3D%3D",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=62057888&dur=163.046&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=299&keepalive=yes&lmt=1705967093743693&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRQIgBEemc0Cvd3KhNooNRblgX64_fjNSP30RmWDfFwDR7qYCIQCXpQ9FO0_X93ZHcyvRZCKX5gbJuusCReaRcJbRLFsM_g%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=1308224&vprv=1&xpc=EgVo2aDSNQ%3D%3D"),
itag: 299,
bitrate: 3926810,
average_bitrate: 3044926,
size: Some(62057888),
last_modified: Some(1705967093743693),
index_range: Some(Range(
start: 740,
end: 1167,
@ -380,15 +407,17 @@ VideoPlayer(
mime: "video/mp4; codecs=\"avc1.64002a\"",
format: mp4,
codec: avc1,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
VideoStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=55300085&dur=163.046&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=303&keepalive=yes&lmt=1705966651743358&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRQIgTZlmOcsLYJ_a9SnVLehXnaoajtreQO97qawEIDPEi8sCIQDKFdtBWWMuQUb9X8H-x92B3q-y0g8TvAPanR95cfklXQ%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=130F224&vprv=1&xpc=EgVo2aDSNQ%3D%3D",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303&bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=55300085&dur=163.046&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=303&keepalive=yes&lmt=1705966651743358&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRQIgTZlmOcsLYJ_a9SnVLehXnaoajtreQO97qawEIDPEi8sCIQDKFdtBWWMuQUb9X8H-x92B3q-y0g8TvAPanR95cfklXQ%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=130F224&vprv=1&xpc=EgVo2aDSNQ%3D%3D"),
itag: 303,
bitrate: 3473307,
average_bitrate: 2713348,
size: Some(55300085),
last_modified: Some(1705966651743358),
index_range: Some(Range(
start: 219,
end: 792,
@ -406,17 +435,19 @@ VideoPlayer(
mime: "video/webm; codecs=\"vp9\"",
format: webm,
codec: vp9,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
],
audio_streams: [
AudioStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=934750&dur=163.061&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=249&keepalive=yes&lmt=1714877357172339&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRQIhAItfaWkRs94vqyae7GR4M1xHoQO2lduvNRFugRSf0h-IAiA9fdLOJMwPI8vAO2C13igyv2qGSpOlKQptS4sN6p5Ffw%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=1308224&vprv=1&xpc=EgVo2aDSNQ%3D%3D",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=934750&dur=163.061&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=249&keepalive=yes&lmt=1714877357172339&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRQIhAItfaWkRs94vqyae7GR4M1xHoQO2lduvNRFugRSf0h-IAiA9fdLOJMwPI8vAO2C13igyv2qGSpOlKQptS4sN6p5Ffw%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=1308224&vprv=1&xpc=EgVo2aDSNQ%3D%3D"),
itag: 249,
bitrate: 53073,
average_bitrate: 45860,
size: 934750,
last_modified: Some(1714877357172339),
index_range: Some(Range(
start: 266,
end: 551,
@ -432,15 +463,17 @@ VideoPlayer(
channels: Some(2),
loudness_db: Some(5.21),
track: None,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
AudioStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=1245582&dur=163.061&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=250&keepalive=yes&lmt=1714877466693058&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRQIgdJ1SjWwaloQecEblSIMFp2qFmpG_kKYZP1vX_M55dE0CIQCDSfa_FsaiFRcNL-1LRTgCIRSO7dj5vrpKR1Ya-KbmMw%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=1308224&vprv=1&xpc=EgVo2aDSNQ%3D%3D",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=1245582&dur=163.061&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=250&keepalive=yes&lmt=1714877466693058&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRQIgdJ1SjWwaloQecEblSIMFp2qFmpG_kKYZP1vX_M55dE0CIQCDSfa_FsaiFRcNL-1LRTgCIRSO7dj5vrpKR1Ya-KbmMw%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=1308224&vprv=1&xpc=EgVo2aDSNQ%3D%3D"),
itag: 250,
bitrate: 71197,
average_bitrate: 61109,
size: 1245582,
last_modified: Some(1714877466693058),
index_range: Some(Range(
start: 266,
end: 551,
@ -456,15 +489,17 @@ VideoPlayer(
channels: Some(2),
loudness_db: Some(5.21),
track: None,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
AudioStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=2640283&dur=163.096&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=140&keepalive=yes&lmt=1705966477945761&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRAIgSxdbLrbojMVJcyRzsI2TrzOf78LN28bWcsHpbs4QXDwCIHidfXoriWMHfuiktUCdzLuUmksU7r5vITdh6u0puNmx&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=1308224&vprv=1&xpc=EgVo2aDSNQ%3D%3D",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=2640283&dur=163.096&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=140&keepalive=yes&lmt=1705966477945761&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fmp4&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRAIgSxdbLrbojMVJcyRzsI2TrzOf78LN28bWcsHpbs4QXDwCIHidfXoriWMHfuiktUCdzLuUmksU7r5vITdh6u0puNmx&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=1308224&vprv=1&xpc=EgVo2aDSNQ%3D%3D"),
itag: 140,
bitrate: 130268,
average_bitrate: 129508,
size: 2640283,
last_modified: Some(1705966477945761),
index_range: Some(Range(
start: 632,
end: 867,
@ -480,15 +515,17 @@ VideoPlayer(
channels: Some(2),
loudness_db: Some(5.2200003),
track: None,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
AudioStream(
url: "https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=2480393&dur=163.061&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=251&keepalive=yes&lmt=1714877359450110&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRQIgO0jG-x2l6AF7tjryIX_oM3np78WgNDiseezppLfbQrgCIQCVLdpDhclKc8vQgWGzKXcqsAxgNl5S3MlLT8u1Jeok2A%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=1308224&vprv=1&xpc=EgVo2aDSNQ%3D%3D",
url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?bui=AXc671IvQBUNCtxNiAkj0M-Bvcb-N5cUu1XFk68f4Cnj0sFLEy1sixyW5lThzLYJXioG8kVQ2xT9KNLS&c=TVHTML5&clen=2480393&dur=163.061&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&gir=yes&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&itag=251&keepalive=yes&lmt=1714877359450110&lmw=1&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mime=audio%2Fwebm&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=SWvqB0UTkUvifuM&ns=ZR8RwjQ3VJGDvQifdaM1IRMQ&pl=26&requiressl=yes&rqh=1&sefc=1&sig=AJfQdSswRQIgO0jG-x2l6AF7tjryIX_oM3np78WgNDiseezppLfbQrgCIQCVLdpDhclKc8vQgWGzKXcqsAxgNl5S3MlLT8u1Jeok2A%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cxpc%2Cbui%2Cvprv%2Csvpuc%2Cmime%2Cns%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&svpuc=1&txp=1308224&vprv=1&xpc=EgVo2aDSNQ%3D%3D"),
itag: 251,
bitrate: 140833,
average_bitrate: 121691,
size: 2480393,
last_modified: Some(1714877359450110),
index_range: Some(Range(
start: 266,
end: 551,
@ -504,6 +541,7 @@ VideoPlayer(
channels: Some(2),
loudness_db: Some(5.21),
track: None,
xtags: None,
drm_track_type: None,
drm_systems: [],
),
@ -520,6 +558,9 @@ VideoPlayer(
valid_until: "[date]",
hls_manifest_url: None,
dash_manifest_url: None,
abr_streaming_url: Some("https://rr5---sn-h0jeenek.googlevideo.com/videoplayback?c=TVHTML5&ei=viioZtTdKteHi9oPl42KsAg&expire=1722318110&fvip=4&id=o-AC7iotZ_nCvg7C6fK7ofX174GXVOdwW68lsyXLLmCs0h&initcwndbps=1957500&ip=93.235.183.158&keepalive=yes&lsig=AGtxev0wRgIhANyFV4Ji7jlkXvfkb_czMQDZCiu6AbJ3Kzyv_s9V9WyvAiEA0o8XuM9kyh98hG1yg7h44L3I5OAUXuTpQdjxUaZ1V4A%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=mQ&mm=31%2C29&mn=sn-h0jeenek%2Csn-h0jelnez&ms=au%2Crdu&mt=1722295996&mv=m&mvi=5&n=eBXmY26Y0c3VPyt&ns=Kl83P0QZk1oI9742KUD7ly8Q&pl=26&requiressl=yes&rqh=1&sabr=1&sig=AJfQdSswRAIgJRK55pIkQ3Pak9jZ4fHPDsxXv0YgkxKE-FFdIN12ph8CIFHlFEvAoUOoX4Fd1RmyCJqgLZhDkSLwD6s-xVW25kYL&smc=1&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Csource%2Crequiressl%2Cxpc%2Csvpuc%2Cns%2Csabr%2Crqh&svpuc=1&xpc=EgVo2aDSNQ%3D%3D"),
abr_ustreamer_config: Some("CswJCpcHCAAlAACAPy1SuF4_NQAAwD9YAWABaAFyFgoSbWZzMl9jbWZzX3YzXzJfMTA5GAB4j06gAQGoAQCQAgG4AgDIAgHaAroBELDqARioRiCgnAEoiCcwmHVwiCeAAfQDuAEB4AEDkAIBmAIMoAIBwAIB0AIC4AIB6AIEgAMCiAOIJ5gDAagDA8ADAcgDAdADAfgDAYAEAYgEAZAEAZgEAaAEAagEAcgEAdAEAdgEAeAEAOgEAfgEB4AFfYgFAbAFAbgFAcAFAcgFAdAFAdgFAeAF0A_oBQH4BdAPgAYBuAYBwAYB0AYB2AYB6AYB8AYB-AYBkAcBqAcB2AcB-LWR5QwB-gKeAi0AAIJCNQAAlkJIAWUAAIBAaMBwqAHQhgOwAeADuAEBzQEAAIA_8AEB_QEAAIA_hQKamRk-jQIAAIA_lQIAAAJCmAIBtQIAAIA_wALgA9ICEbD__________wEePEZaXF1e2gIFMjA6MDDgAnjoAugC9QIK16M7_QLNzMw9gAMBkAMBnQMK1yM9oAMBuAMByAMB2AMB5QNiSkRA7QMyyvM-8AMB_QNmZoY_hQQAAIBAmAQB1QQAACBB6ATwEPAEAb0Fo0Afu8UF308tP8gFAeAFAZgGAaAGAagGAbUGvTeGNb0GMzODQJAHAcAHAcgHAdUHAICdQ-UHAIAJRKEIAAAAAAAA8L-pCAAAAAAAAPC_sAjwAbgIAdgI8AHoCAGCAwCQAwGoAwGwAwPQAwHYAwHgA5BOuAQBygQcChMIwKkHEJh1GOgHJQAAAAAoADAAEODUAxjQD9IEDQoICLAJELAJIAEgiCfaBAsKBgjwLhDwLiCIJ-gEAfgEAYAFAYgFAZAFAagFAbAFAdAFAdgFAegFAfAFAYgGAZgGAagGgIACwAYByAYB0gYUCOgHEGQaDQiIJxUAAAA_Hc3MTD-CBwoVAACAPxhkIJBOiAcBoAcBsAcBuAcBwAcB-AcBgAgBoAgBsAgBuAgB0ggGCAEQARgBmAkBqQkAAAAAAADwv7EJAAAAAAAA8L_ICQHaCSRFRzRmTDl1Sm9tL2NWdklmNjg4bnB6c2t4SVQrMXl0N09POHXgCQGwCgHYCgHwCgGICwGYCwG4CwHICwHQCwHYCwHqCwSLBowG8AsB-AsBkAwBoAwBqAyQAbAMAbgMAcAMAdAMAeAMAegMAYANAaANAdANAeANAYgOAZAOAbAOAYinocoLARgBIAEyDAirAhDNgPzUlvKDAzIMCK8CEP64moKV8oMDMgwIiAEQ7Mj0upfygwMyDAj3ARCNxJTwlPKDAzIMCKoCEIW7uNSW8oMDMgwIrgIQn5LUz5TygwMyDAiHARD5xP-ul_KDAzIMCPQBEOmKifSU8oMDMgwIhgEQk_6DsZfygwMyDAjzARCSyIT1lPKDAzIMCIUBEJWg47aX8oMDMgwI8gEQ3_PN8JTygwMyDAigARC-zf6xl_KDAzIMCJYCENPIofOU8oMDMgwIjAEQodeqr5TygwMyDAj5ARDzzNT9v_WFAzIMCPoBEMKb8bHA9YUDMgwI-wEQ_s_f_r_1hQM6AEgAUiYaAmVuKAAyGFVDYnh4RWktSW1QbGJMeDVGLWZIZXRFZzgAQABYAJDL048OARJNAKEYC4YwRgIhAIj4Ug4dw_gq15NXvgcfXpI1Fm_fhmwl-4ad-rX3Ffg_AiEAkZDsUgoAGLOXIvWZlNyuyfu8HLWt-snFl3gkTiPo2acaAmVp"),
po_token: None,
preview_frames: [
Frameset(
url_template: "https://i.ytimg.com/sb/pPvd8UxmSbQ/storyboard3_L0/default.jpg?sqp=-oaymwGbA0g48quKqQOSA4gBAZUBAAAEQpgBMqABPKgBBLABELABDbABDLABELABFbABH7ABJrABLbABDrABDrABD7ABErABF7ABK7ABLLABKbABD7ABDrABELABFbABH7ABKrABMrABKbABD7ABEbABFLABGLABJrABPbABOLABLbABEbABFLABHrABKrABMbABS7ABR7ABNrABFbABHLABKbABLrABObABR7ABTbABP7ABJbABLrABN7ABPbABR7ABUrABUbABRbABM7ABQLABQrABQ7ABTLABRLABRrABQ7gBEbgBEbgBFbgBI7gBRLgBQ7gBQ7gBQ7gBEbgBE7gBFrgBL7gBQ7gBQ7gBQ7gBQ7gBFbgBFrgBKbgBQ7gBQ7gBQ7gBQ7gBQ7gBI7gBL7gBQ7gBQ7gBQ7gBQ7gBQ7gBQ7gBRLgBQ7gBQ7gBQ7gBQ7gBQ7gBQrgBQrgBQ7gBQ7gBQ7gBQ7gBQ7gBQrgBQrgBQrgBQ7gBQ7gBQ7gBQ7gBQrgBQrgBQrgBQrgBQ7gBQ7gBQ7gBQrgBQrgBQrgBQrgBQqLzl_8DBgjf8LPxBQ==&sigh=rs$AOn4CLCsCT8Lprh2S0ptmCRsWH7VtDl3YQ",

View file

@ -147,6 +147,12 @@ pub struct VideoPlayer {
pub hls_manifest_url: Option<String>,
/// Dash manifest URL (for livestreams)
pub dash_manifest_url: Option<String>,
/// ABR (adaptive bitrate) streaming URL
pub abr_streaming_url: Option<String>,
/// ABR streaming config
pub abr_ustreamer_config: Option<String>,
/// PO token for fetching ABR stream
pub po_token: Option<String>,
/// Video frames for seek preview
pub preview_frames: Vec<Frameset>,
/// Video player DRM config
@ -194,7 +200,7 @@ pub struct VideoPlayerDetails {
#[non_exhaustive]
pub struct VideoStream {
/// Video stream URL
pub url: String,
pub url: Option<String>,
/// YouTube stream format identifier
pub itag: u32,
/// Stream bitrate (in bits/second)
@ -203,6 +209,8 @@ pub struct VideoStream {
pub average_bitrate: u32,
/// Video file size in bytes
pub size: Option<u64>,
/// Last modified timestamp
pub last_modified: Option<u64>,
/// Index range (used for DASH streaming)
pub index_range: Option<Range<u32>>,
/// Init range (used for DASH streaming)
@ -225,6 +233,8 @@ pub struct VideoStream {
pub format: VideoFormat,
/// Video codec
pub codec: VideoCodec,
/// Stream tags
pub xtags: Option<String>,
/// DRM track type
///
/// [`None`] if the track is not DRM-protected
@ -238,7 +248,7 @@ pub struct VideoStream {
#[non_exhaustive]
pub struct AudioStream {
/// Audio stream URL
pub url: String,
pub url: Option<String>,
/// YouTube stream format identifier
pub itag: u32,
/// Stream bitrate (in bits/second)
@ -247,6 +257,8 @@ pub struct AudioStream {
pub average_bitrate: u32,
/// Audio file size in bytes
pub size: u64,
/// Last modified timestamp
pub last_modified: Option<u64>,
/// Index range (used for DASH streaming)
pub index_range: Option<Range<u32>>,
/// Init range (used for DASH streaming)
@ -276,6 +288,11 @@ pub struct AudioStream {
///
/// The loudness parameter is not available when using the Android client.
pub loudness_db: Option<f32>,
/// True if the audio stream uses dynamic range compression (Stable Volume option)
///
/// <https://support.google.com/youtube/answer/14106294?hl=en>
/// <https://en.wikipedia.org/wiki/Dynamic_range_compression>
pub is_drc: bool,
/// Audio track information
///
/// Videos can have multiple audio tracks (different languages).
@ -283,6 +300,8 @@ pub struct AudioStream {
///
/// This is None if the video contains only 1 audio track.
pub track: Option<AudioTrack>,
/// Stream tags (e.g. audio track language and type)
pub xtags: Option<String>,
/// DRM track type
///
/// [`None`] if the track is not DRM-protected

View file

@ -36,6 +36,7 @@ impl QualityOrd for AudioStream {
.as_ref()
.map(|t| track_type_rating(t.track_type)),
)
.then_with(|| other.is_drc.cmp(&self.is_drc))
.then_with(|| self.channels.cmp(&other.channels))
.then_with(|| cmp_bitrate(self).cmp(&cmp_bitrate(other)))
}

View file

@ -9,7 +9,7 @@ use super::*;
/// Trait for YouTube streams (video and audio)
pub trait YtStream {
/// Stream URL
fn url(&self) -> &str;
fn url(&self) -> Option<&str>;
/// YouTube stream format identifier
fn itag(&self) -> u32;
/// Stream bitrate (in bits/second)
@ -18,6 +18,8 @@ pub trait YtStream {
fn averate_bitrate(&self) -> u32;
/// File size in bytes
fn size(&self) -> Option<u64>;
/// Last modified timestamp
fn last_modified(&self) -> Option<u64>;
/// Index range (used for DASH streaming)
fn index_range(&self) -> Option<Range<u32>>;
/// Init range (used for DASH streaming)
@ -26,11 +28,13 @@ pub trait YtStream {
fn duration_ms(&self) -> Option<u32>;
/// MIME file type
fn mime(&self) -> &str;
/// Stream tags (e.g. audio track language and type)
fn xtags(&self) -> Option<&str>;
}
impl YtStream for VideoStream {
fn url(&self) -> &str {
&self.url
fn url(&self) -> Option<&str> {
self.url.as_deref()
}
fn itag(&self) -> u32 {
@ -49,6 +53,10 @@ impl YtStream for VideoStream {
self.size
}
fn last_modified(&self) -> Option<u64> {
self.last_modified
}
fn index_range(&self) -> Option<Range<u32>> {
self.index_range.clone()
}
@ -64,11 +72,15 @@ impl YtStream for VideoStream {
fn mime(&self) -> &str {
&self.mime
}
fn xtags(&self) -> Option<&str> {
self.xtags.as_deref()
}
}
impl YtStream for AudioStream {
fn url(&self) -> &str {
&self.url
fn url(&self) -> Option<&str> {
self.url.as_deref()
}
fn itag(&self) -> u32 {
@ -87,6 +99,10 @@ impl YtStream for AudioStream {
Some(self.size)
}
fn last_modified(&self) -> Option<u64> {
self.last_modified
}
fn index_range(&self) -> Option<Range<u32>> {
self.index_range.clone()
}
@ -102,6 +118,10 @@ impl YtStream for AudioStream {
fn mime(&self) -> &str {
&self.mime
}
fn xtags(&self) -> Option<&str> {
self.xtags.as_deref()
}
}
/// Trait for file types

View file

@ -17,17 +17,19 @@ pub struct StreamFilter {
audio_languages: Vec<String>,
audio_autodub: bool,
audio_descriptive: bool,
audio_drc: bool,
video_max_res: Option<u32>,
video_max_fps: Option<u8>,
video_formats: Option<Vec<VideoFormat>>,
video_codecs: Option<Vec<VideoCodec>>,
video_hdr: bool,
video_none: bool,
abr_only: bool,
drm_track_types: Vec<DrmTrackType>,
drm_system: Option<DrmSystem>,
}
const N_RES_AUDIO: usize = 4;
const N_RES_AUDIO: usize = 5;
const N_RES_VIDEO: usize = 5;
type AudioRes = Option<[i64; N_RES_AUDIO]>;
type VideoRes = Option<[i64; N_RES_VIDEO]>;
@ -109,6 +111,16 @@ impl StreamFilter {
self
}
/// Prefer audio streams that use dynamic range compression (Stable volume).
///
/// <https://support.google.com/youtube/answer/14106294?hl=en>
/// <https://en.wikipedia.org/wiki/Dynamic_range_compression>
#[must_use]
pub fn audio_drc(mut self) -> Self {
self.audio_drc = true;
self
}
/// Set the maximum video resolution. Resolution is determined by the
/// pixel count of the shorter edge (e.g. 1080p).
///
@ -176,6 +188,13 @@ impl StreamFilter {
self
}
/// Allow ABR-only streams without URL
#[must_use]
pub fn allow_abr_only(mut self) -> Self {
self.abr_only = true;
self
}
fn check_drm(&self, track_type: Option<DrmTrackType>, drm_systems: &[DrmSystem]) -> Option<()> {
if let Some(track_type) = track_type {
if !self.drm_track_types.contains(&track_type) {
@ -189,6 +208,10 @@ impl StreamFilter {
}
fn apply_audio(&self, stream: &AudioStream) -> AudioRes {
if stream.url.is_none() && !self.abr_only {
return None;
}
let bitrate = match self.audio_max_bitrate {
Some(max) => {
if stream.average_bitrate > max {
@ -259,6 +282,8 @@ impl StreamFilter {
None => 0,
};
let drc = i64::from(self.audio_drc == stream.is_drc);
let channels = stream.channels.unwrap_or_default();
if let Some(max_channels) = self.audio_max_channels {
if channels > max_channels {
@ -268,10 +293,14 @@ impl StreamFilter {
self.check_drm(stream.drm_track_type, &stream.drm_systems)?;
Some([language, track_type, channels.into(), bitrate])
Some([language, track_type, drc, channels.into(), bitrate])
}
fn apply_video(&self, stream: &VideoStream) -> VideoRes {
fn apply_video(&self, stream: &VideoStream, video_only: bool) -> VideoRes {
if stream.url.is_none() && !(self.abr_only && video_only) {
return None;
}
let vres = stream.height.min(stream.width);
let res = match self.video_max_res {
Some(max) => filter_max(vres, max),
@ -347,6 +376,7 @@ impl VideoPlayer {
fn _select_video_stream<'a>(
streams: &'a [VideoStream],
filter: &StreamFilter,
video_only: bool,
) -> Option<&'a VideoStream> {
if filter.video_none {
return None;
@ -354,19 +384,19 @@ impl VideoPlayer {
streams
.iter()
.filter_map(|s| filter.apply_video(s).map(|r| (s, r)))
.filter_map(|s| filter.apply_video(s, video_only).map(|r| (s, r)))
.max_by_key(|(_, r)| *r)
.map(|(s, _)| s)
}
/// Select the video stream which is the best match for the given [`StreamFilter`]
pub fn select_video_stream(&self, filter: &StreamFilter) -> Option<&VideoStream> {
Self::_select_video_stream(&self.video_streams, filter)
Self::_select_video_stream(&self.video_streams, filter, false)
}
/// Select the video-only stream which is the best match for the given [`StreamFilter`]
pub fn select_video_only_stream(&self, filter: &StreamFilter) -> Option<&VideoStream> {
Self::_select_video_stream(&self.video_only_streams, filter)
Self::_select_video_stream(&self.video_only_streams, filter, true)
}
/// Select a video and audio stream which is the best match for the given [`StreamFilter`]
@ -466,7 +496,7 @@ mod tests {
let selection = player.select_audio_stream(&filter);
match expect_url {
Some(expect_url) => assert_eq!(selection.unwrap().url, expect_url),
Some(expect_url) => assert_eq!(selection.unwrap().url.as_deref().unwrap(), expect_url),
None => assert_eq!(selection, None),
}
}
@ -491,7 +521,7 @@ mod tests {
let selection = player.select_video_only_stream(&filter);
match expect_url {
Some(expect_url) => assert_eq!(selection.unwrap().url, expect_url),
Some(expect_url) => assert_eq!(selection.unwrap().url.as_deref().unwrap(), expect_url),
None => assert_eq!(selection, None),
}
}
@ -526,12 +556,12 @@ mod tests {
let (video, audio) = PLAYER_HDR.select_video_audio_stream(&filter);
match expect_video_url {
Some(expect_url) => assert_eq!(video.unwrap().url, expect_url),
Some(expect_url) => assert_eq!(video.unwrap().url.as_deref().unwrap(), expect_url),
None => assert_eq!(video, None),
}
match expect_audio_url {
Some(expect_url) => assert_eq!(audio.unwrap().url, expect_url),
Some(expect_url) => assert_eq!(audio.unwrap().url.as_deref().unwrap(), expect_url),
None => assert_eq!(audio, None),
}
}

View file

@ -6,7 +6,7 @@ pub mod dictionary;
pub mod timeago;
pub use date::{now_sec, shift_months, shift_weeks_monday, shift_years};
pub use protobuf::{string_from_pb, ProtoBuilder};
pub use protobuf::{kv_from_pb, string_from_pb, ProtoBuilder};
pub use visitor_data::VisitorDataCache;
use std::{
@ -486,7 +486,17 @@ pub fn b64_encode<T: AsRef<[u8]>>(input: T) -> String {
}
pub fn b64_decode<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>, data_encoding::DecodeError> {
data_encoding::BASE64URL.decode(input.as_ref())
// Remove trailing padding
let mut x = input.as_ref();
while !x.is_empty() {
let li = x.len() - 1;
if x[li] == b'=' {
x = &x[0..li];
} else {
break;
}
}
data_encoding::BASE64URL_NOPAD.decode(x)
}
/// Get the country from its English name

View file

@ -1,3 +1,5 @@
use std::collections::HashMap;
/// [`ProtoBuilder`] is used to construct protobuf messages using a builder pattern
#[derive(Debug, Default)]
pub struct ProtoBuilder {
@ -100,7 +102,7 @@ fn parse_varint<P: Iterator<Item = u8>>(pb: &mut P) -> Option<u64> {
}
}
fn parse_field<P: Iterator<Item = u8>>(pb: &mut P) -> Option<(u32, u8)> {
fn parse_field(pb: &mut impl Iterator<Item = u8>) -> Option<(u32, u8)> {
parse_varint(pb).map(|v| {
let f = (v >> 3) as u32;
let w = (v & 0x07) as u8;
@ -108,13 +110,12 @@ fn parse_field<P: Iterator<Item = u8>>(pb: &mut P) -> Option<(u32, u8)> {
})
}
pub fn string_from_pb<P: IntoIterator<Item = u8>>(pb: P, field: u32) -> Option<String> {
let mut pb = pb.into_iter();
while let Some((this_field, wire)) = parse_field(&mut pb) {
fn next_string_from_pb(pb: &mut impl Iterator<Item = u8>, filter: &[u32]) -> Option<(u32, String)> {
while let Some((this_field, wire)) = parse_field(pb) {
let to_skip = match wire {
// varint
0 => {
parse_varint(&mut pb);
parse_varint(pb);
0
}
// fixed 64bit
@ -123,15 +124,16 @@ pub fn string_from_pb<P: IntoIterator<Item = u8>>(pb: P, field: u32) -> Option<S
5 => 4,
// string
2 => {
let len = parse_varint(&mut pb)?;
if this_field == field {
let len = parse_varint(pb)?;
if filter.contains(&this_field) {
let mut buf = Vec::new();
for _ in 0..len {
buf.push(pb.next()?);
}
return String::from_utf8(buf).ok();
return Some((this_field, String::from_utf8(buf).ok()?));
} else {
len
}
len
}
_ => return None,
};
@ -142,6 +144,64 @@ pub fn string_from_pb<P: IntoIterator<Item = u8>>(pb: P, field: u32) -> Option<S
None
}
/// Extract a string with the given field number from a protobuf object
pub fn string_from_pb<P: IntoIterator<Item = u8>>(pb: P, field: u32) -> Option<String> {
let mut pb = pb.into_iter();
next_string_from_pb(&mut pb, &[field]).map(|x| x.1)
}
/// Parse a protobuf-encoded key/value map
///
/// K/V pairs are embedded structs of field 1
/// K/V struct: {key = 1; value = 2}
pub fn kv_from_pb<P: IntoIterator<Item = u8>>(pb: P) -> Option<HashMap<String, String>> {
let mut res = HashMap::new();
let pb = &mut (pb.into_iter());
while let Some((this_field, wire)) = parse_field(pb) {
let to_skip = match wire {
// varint
0 => {
parse_varint(pb);
0
}
// fixed 64bit
1 => 8,
// fixed 32bit
5 => 4,
// embed
2 => {
let len = parse_varint(pb)?;
if this_field == 1 {
let mut tsh = pb.take(len as usize);
let (f1, v1) = next_string_from_pb(&mut tsh, &[1, 2])?;
let (f2, v2) = next_string_from_pb(&mut tsh, &[1, 2])?;
let rem = tsh.size_hint().1.unwrap_or_default();
if rem > 0 {
tsh.nth(rem - 1);
}
let (k, v) = if f1 == 1 && f2 == 2 {
(v1, v2)
} else if f2 == 1 && f1 == 2 {
(v2, v1)
} else {
return None;
};
res.insert(k, v);
0
} else {
len
}
}
_ => return None,
};
for _ in 0..to_skip {
pb.next();
}
}
Some(res)
}
#[cfg(test)]
mod tests {
use crate::util;
@ -170,4 +230,15 @@ mod tests {
let res = string_from_pb(p_bytes, 3).unwrap();
assert_eq!(res, "UC9vrvNSL3xcWGSkV86REBSg");
}
#[test]
fn parse_proto_kv() {
let p = "ChEKBWFjb250EghvcmlnaW5hbAoKCgRsYW5nEgJlbg";
let p_bytes = util::b64_decode(urlencoding::decode(p).unwrap().as_bytes()).unwrap();
let res = kv_from_pb(p_bytes).unwrap();
assert_eq!(res.len(), 2);
assert_eq!(res["acont"], "original");
assert_eq!(res["lang"], "en");
}
}

View file

@ -110,7 +110,7 @@ impl VisitorDataCache {
/// Fetch a new visitor data ID and store it in the cache
pub async fn new_visitor_data(&self) -> Result<String, Error> {
let vd = self.fetch_visitor_data().await.unwrap();
let vd = self.fetch_visitor_data().await?;
self.inner
.req_counter

View file

@ -149,7 +149,7 @@ async fn check_video_stream(s: impl YtStream) {
let http = reqwest::Client::new();
let resp = http
.get(s.url())
.get(s.url().expect("no url"))
.send()
.await
.unwrap()