播放器数据结构与功能模块(一)Chunk (块) —— 加载层视角

Chunk 是 Media3 内部 ChunkSource 和 ChunkSampleStream 处理的对象实例。

定义: 在代码中,Chunk 是一个抽象类,代表了一次完整的 HTTP 加载任务。

特性:一个 Chunk 通常对应一个 Segment,但并不绝对。

作用: 封装了数据加载的状态(从哪下载、下载了多少、数据读到了哪个 Buffer)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/** 可使用 {@link Loader} 进行加载的对象接口。 */
public interface Loadable {

/**
* 取消加载任务。
*
* <p>Loadable 的实现类应确保在调用此方法后,当前正在执行的 {@link #load()} 调用能迅速退出。
* {@link #load()} 可以通过正常返回或抛出 {@link IOException} 来退出。
*
* <p>如果当前有正在执行的 {@link #load()} 调用,则在调用此方法后,执行该调用的线程将被立即中断。
* 因此,实现类不需要(也不应该尝试)自行中断加载线程。
*
* <p>尽管加载线程会被中断,但 Loadable 的实现类不应在 {@link #load()} 中使用线程的中断状态
* 来判断加载是否已被取消。这种方法并不健壮。相反,实现类应该使用自己的标志(例如使用 {@link AtomicBoolean})
* 来发出取消信号。
*/
void cancelLoad();

/**
* 执行加载操作,在完成或取消时返回。
*
* @throws IOException 如果输入内容无法加载。
*/
void load() throws IOException;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package androidx.media3.exoplayer.source.chunk;

import static com.google.common.base.Preconditions.checkNotNull;

import android.net.Uri;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.C.DataType;
import androidx.media3.common.Format;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DataSpec;
import androidx.media3.datasource.StatsDataSource;
import androidx.media3.exoplayer.source.LoadEventInfo;
import androidx.media3.exoplayer.upstream.Loader.Loadable;
import java.util.List;
import java.util.Map;

/**
* 一个抽象基类,实现了 {@link Loadable} 接口,用于加载流播放所需的各种“数据块(Chunks)”。
*/
@UnstableApi
public abstract class Chunk implements Loadable {

/** 标识此加载任务的唯一 ID。 */
public final long loadTaskId;

/** 定义要加载的数据范围(如 URL、偏移量、长度等)的 {@link DataSpec}。 */
public final DataSpec dataSpec;

/** 此分片的数据类型(例如:媒体数据、清单文件、元数据等),仅用于统计和报告。 */
public final @DataType int type;

/** 此分片所属轨道的格式信息(如:分辨率、码率、编码格式等)。 */
public final Format trackFormat;

/**
* 轨道选择的原因(例如:自适应切换、手动选择、初次加载等)。
* 如果该分片不属于特定轨道或原因未知,则为 {@link C#SELECTION_REASON_UNKNOWN}。
*/
public final @C.SelectionReason int trackSelectionReason;

/**
* 轨道选择时的附加可选数据。如果没有相关数据则为 Null。
*/
@Nullable public final Object trackSelectionData;

/**
* 此分片包含的媒体内容的起始时间戳(微秒)。
* 如果加载的数据不包含媒体采样(如加载索引文件),则为 {@link C#TIME_UNSET}。
*/
public final long startTimeUs;

/**
* 此分片包含的媒体内容的结束时间戳(微秒)。
* 如果加载的数据不包含媒体采样,则为 {@link C#TIME_UNSET}。
*/
public final long endTimeUs;

/** 具有统计功能的数据源,用于追踪已读取的字节数、最后的响应头等。 */
protected final StatsDataSource dataSource;

/**
* @param dataSource 执行数据加载的数据源。
* @param dataSpec 定义要加载的数据范围。
* @param type 分片类型。
* @param trackFormat 轨道格式。
* @param trackSelectionReason 轨道选择原因。
* @param trackSelectionData 轨道选择关联数据。
* @param startTimeUs 媒体起始时间。
* @param endTimeUs 媒体结束时间。
*/
public Chunk(
DataSource dataSource,
DataSpec dataSpec,
@DataType int type,
Format trackFormat,
@C.SelectionReason int trackSelectionReason,
@Nullable Object trackSelectionData,
long startTimeUs,
long endTimeUs) {
// 将传入的 dataSource 包装成带统计功能的 StatsDataSource
this.dataSource = new StatsDataSource(dataSource);
this.dataSpec = checkNotNull(dataSpec);
this.type = type;
this.trackFormat = trackFormat;
this.trackSelectionReason = trackSelectionReason;
this.trackSelectionData = trackSelectionData;
this.startTimeUs = startTimeUs;
this.endTimeUs = endTimeUs;
// 为当前任务生成一个新的 ID,用于日志追踪和事件上报
loadTaskId = LoadEventInfo.getNewId();
}

/** 返回此分片的持续时间(微秒)。 */
public final long getDurationUs() {
return endTimeUs - startTimeUs;
}

/**
* 返回已加载的字节数。
* 注意:必须在加载完成、失败或被取消后调用此方法。
*/
public final long bytesLoaded() {
return dataSource.getBytesRead();
}

/**
* 返回最后一次调用 {@link DataSource#open} 关联的 {@link Uri}。
* 如果发生了重定向,则返回重定向后的 URI。
* 注意:必须在加载完成、失败或被取消后调用此方法。
*/
public final Uri getUri() {
return dataSource.getLastOpenedUri();
}

/**
* 返回最后一次 {@link DataSource#open} 调用产生的响应头。
* 注意:必须在加载完成、失败或被取消后调用此方法。
*/
public final Map<String, List<String>> getResponseHeaders() {
return dataSource.getLastResponseHeaders();
}
}
,