Tuỳ chỉnh

Cốt lõi của thư viện ExoPlayer là giao diện Player. Player hiển thị chức năng trình phát nội dung nghe nhìn cấp cao truyền thống, chẳng hạn như khả năng lưu nội dung nghe nhìn vào vùng đệm, phát, tạm dừng và tua. Cách triển khai mặc định ExoPlayer được thiết kế để đưa ra một số giả định về (và do đó áp đặt một số hạn chế đối với) loại nội dung nghe nhìn đang phát, cách thức và vị trí lưu trữ cũng như cách hiển thị nội dung đó. Thay vì trực tiếp triển khai việc tải và kết xuất nội dung nghe nhìn, các phương thức triển khai ExoPlayer sẽ uỷ quyền công việc này cho các thành phần được chèn khi tạo trình phát hoặc khi các nguồn nội dung nghe nhìn mới được truyền đến trình phát. Các thành phần chung cho tất cả các cách triển khai ExoPlayer là:

  • Các thực thể MediaSource xác định nội dung nghe nhìn cần phát, tải nội dung nghe nhìn và từ đó có thể đọc nội dung nghe nhìn đã tải. Một thực thể MediaSource được tạo từ MediaItem bằng MediaSource.Factory bên trong trình phát. Bạn cũng có thể truyền trực tiếp các mục này đến trình phát bằng API danh sách phát dựa trên nguồn nội dung nghe nhìn.
  • Một thực thể MediaSource.Factory chuyển đổi MediaItem thành MediaSource. MediaSource.Factory được chèn khi trình phát được tạo.
  • Renderer hiển thị từng thành phần của nội dung nghe nhìn. Các giá trị này được chèn khi trình phát được tạo.
  • TrackSelector chọn các bản nhạc do MediaSource cung cấp để mỗi Renderer có sẵn sử dụng. TrackSelector được chèn khi trình phát được tạo.
  • LoadControl kiểm soát thời điểm MediaSource lưu nhiều nội dung nghe nhìn hơn vào vùng đệm và lượng nội dung nghe nhìn được lưu vào vùng đệm. LoadControl được chèn khi trình phát được tạo.
  • LivePlaybackSpeedControl kiểm soát tốc độ phát trong khi phát trực tiếp để cho phép người chơi duy trì gần với độ lệch trực tiếp đã định cấu hình. LivePlaybackSpeedControl được chèn khi trình phát được tạo.

Khái niệm chèn các thành phần triển khai các phần chức năng của trình phát có trong toàn bộ thư viện. Các phương thức triển khai mặc định của một số thành phần sẽ uỷ quyền công việc cho các thành phần được chèn thêm. Điều này cho phép nhiều thành phần phụ được thay thế riêng lẻ bằng các phương thức triển khai được định cấu hình theo cách tuỳ chỉnh.

Tuỳ chỉnh trình phát

Một số ví dụ phổ biến về việc tuỳ chỉnh trình phát bằng cách chèn thành phần được mô tả trong các phần sau.

Định cấu hình bộ phần cứng và phần mềm mạng

Chúng tôi có một trang về cách tuỳ chỉnh bộ phần cứng và phần mềm mạng mà ExoPlayer sử dụng.

Lưu dữ liệu được tải từ mạng vào bộ nhớ đệm

Xem hướng dẫn về lưu vào bộ nhớ đệm nhanh tạm thời và tải nội dung nghe nhìn xuống.

Tuỳ chỉnh hoạt động tương tác với máy chủ

Một số ứng dụng có thể muốn chặn các yêu cầu HTTP và phản hồi. Bạn có thể muốn chèn tiêu đề của yêu cầu tuỳ chỉnh, đọc tiêu đề phản hồi của máy chủ, sửa đổi URI của yêu cầu, v.v. Ví dụ: ứng dụng của bạn có thể tự xác thực bằng cách chèn mã thông báo làm tiêu đề khi yêu cầu phân đoạn nội dung nghe nhìn.

Ví dụ sau đây minh hoạ cách triển khai các hành vi này bằng cách chèn DataSource.Factory tuỳ chỉnh vào DefaultMediaSourceFactory:

Kotlin

val dataSourceFactory = DataSource.Factory {
  val dataSource = httpDataSourceFactory.createDataSource()
  // Set a custom authentication request header.
  dataSource.setRequestProperty("Header", "Value")
  dataSource
}
val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(
      DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory)
    )
    .build()

Java

DataSource.Factory dataSourceFactory =
    () -> {
      HttpDataSource dataSource = httpDataSourceFactory.createDataSource();
      // Set a custom authentication request header.
      dataSource.setRequestProperty("Header", "Value");
      return dataSource;
    };

ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            new DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory))
        .build();

Trong đoạn mã trước, HttpDataSource được chèn sẽ bao gồm tiêu đề "Header: Value" trong mọi yêu cầu HTTP. Hành vi này được cố định cho mọi hoạt động tương tác với một nguồn HTTP.

Để có cách tiếp cận chi tiết hơn, bạn có thể chèn hành vi đúng lúc bằng cách sử dụng ResolvingDataSource. Đoạn mã sau đây cho biết cách chèn tiêu đề yêu cầu ngay trước khi tương tác với nguồn HTTP:

Kotlin

val dataSourceFactory: DataSource.Factory =
  ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec ->
    // Provide just-in-time request headers.
    dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri))
  }

Java

DataSource.Factory dataSourceFactory =
    new ResolvingDataSource.Factory(
        httpDataSourceFactory,
        // Provide just-in-time request headers.
        dataSpec -> dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri)));

Bạn cũng có thể sử dụng ResolvingDataSource để thực hiện các sửa đổi đúng lúc của URI, như trong đoạn mã sau:

Kotlin

val dataSourceFactory: DataSource.Factory =
  ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec ->
    // Provide just-in-time URI resolution logic.
    dataSpec.withUri(resolveUri(dataSpec.uri))
  }

Java

DataSource.Factory dataSourceFactory =
    new ResolvingDataSource.Factory(
        httpDataSourceFactory,
        // Provide just-in-time URI resolution logic.
        dataSpec -> dataSpec.withUri(resolveUri(dataSpec.uri)));

Tuỳ chỉnh cách xử lý lỗi

Việc triển khai LoadErrorHandlingPolicy tuỳ chỉnh cho phép các ứng dụng tuỳ chỉnh cách ExoPlayer phản ứng với lỗi tải. Ví dụ: một ứng dụng có thể muốn nhanh chóng báo lỗi thay vì thử lại nhiều lần, hoặc có thể muốn tuỳ chỉnh logic thời gian đợi để kiểm soát thời gian trình phát chờ giữa mỗi lần thử lại. Đoạn mã sau đây cho biết cách triển khai logic đợi thời gian đợi tuỳ chỉnh:

Kotlin

val loadErrorHandlingPolicy: LoadErrorHandlingPolicy =
  object : DefaultLoadErrorHandlingPolicy() {
    override fun getRetryDelayMsFor(
      loadErrorInfo: LoadErrorHandlingPolicy.LoadErrorInfo
    ): Long {
      // Implement custom back-off logic here.
      return 0
    }
  }
val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(
      DefaultMediaSourceFactory(context).setLoadErrorHandlingPolicy(loadErrorHandlingPolicy)
    )
    .build()

Java

LoadErrorHandlingPolicy loadErrorHandlingPolicy =
    new DefaultLoadErrorHandlingPolicy() {
      @Override
      public long getRetryDelayMsFor(LoadErrorHandlingPolicy.LoadErrorInfo loadErrorInfo) {
        // Implement custom back-off logic here.
        return 0;
      }
    };

ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            new DefaultMediaSourceFactory(context)
                .setLoadErrorHandlingPolicy(loadErrorHandlingPolicy))
        .build();

Đối số LoadErrorInfo chứa thêm thông tin về lượt tải không thành công để tuỳ chỉnh logic dựa trên loại lỗi hoặc yêu cầu không thành công.

Tuỳ chỉnh cờ trình trích xuất

Bạn có thể dùng cờ trình trích xuất để tuỳ chỉnh cách trích xuất từng định dạng từ nội dung nghe nhìn tăng dần. Bạn có thể đặt các thuộc tính này trên DefaultExtractorsFactory được cung cấp cho DefaultMediaSourceFactory. Ví dụ sau đây truyền một cờ cho phép tìm kiếm dựa trên chỉ mục cho các luồng MP3.

Kotlin

val extractorsFactory =
  DefaultExtractorsFactory().setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING)
val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(DefaultMediaSourceFactory(context, extractorsFactory))
    .build()

Java

DefaultExtractorsFactory extractorsFactory =
    new DefaultExtractorsFactory().setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING);

ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(new DefaultMediaSourceFactory(context, extractorsFactory))
        .build();

Bật tính năng tua theo tốc độ bit không đổi

Đối với các luồng MP3, ADTS và AMR, bạn có thể bật tính năng tua gần đúng bằng cách sử dụng giả định tốc độ bit không đổi với cờ FLAG_ENABLE_CONSTANT_BITRATE_SEEKING. Bạn có thể đặt các cờ này cho từng trình trích xuất bằng cách sử dụng các phương thức DefaultExtractorsFactory.setXyzExtractorFlags riêng lẻ như mô tả ở trên. Để bật tính năng tìm kiếm tốc độ bit không đổi cho tất cả trình trích xuất hỗ trợ tính năng này, hãy sử dụng DefaultExtractorsFactory.setConstantBitrateSeekingEnabled.

Kotlin

val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)

Java

DefaultExtractorsFactory extractorsFactory =
    new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true);

Sau đó, bạn có thể chèn ExtractorsFactory bằng DefaultMediaSourceFactory như mô tả trong phần tuỳ chỉnh cờ trình trích xuất ở trên.

Bật tính năng xếp hàng đợi bộ đệm không đồng bộ

Hàng đợi bộ đệm không đồng bộ là một tính năng nâng cao trong quy trình kết xuất của ExoPlayer, giúp vận hành các thực thể MediaCodec ở chế độ không đồng bộ và sử dụng các luồng bổ sung để lên lịch giải mã và kết xuất dữ liệu. Việc bật tính năng này có thể giảm số khung hình bị bỏ qua và tình trạng thiếu dữ liệu âm thanh.

Hàng đợi bộ đệm không đồng bộ được bật theo mặc định trên các thiết bị chạy Android 12 (cấp độ API 31) trở lên và có thể được bật theo cách thủ công kể từ Android 6.0 (cấp độ API 23). Hãy cân nhắc bật tính năng này cho những thiết bị cụ thể mà bạn nhận thấy có khung hình bị rớt hoặc thiếu âm thanh, đặc biệt là khi phát nội dung được bảo vệ bằng DRM hoặc nội dung có tốc độ khung hình cao.

Trong trường hợp đơn giản nhất, bạn cần chèn một DefaultRenderersFactory vào trình phát như sau:

Kotlin

val renderersFactory =
  DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing()
val exoPlayer = ExoPlayer.Builder(context, renderersFactory).build()

Java

DefaultRenderersFactory renderersFactory =
    new DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing();
ExoPlayer exoPlayer = new ExoPlayer.Builder(context, renderersFactory).build();

Nếu bạn đang trực tiếp tạo thực thể cho trình kết xuất, hãy truyền new DefaultMediaCodecAdapter.Factory(context).forceEnableAsynchronous() đến hàm khởi tạo MediaCodecVideoRenderer và MediaCodecAudioRenderer.

Tuỳ chỉnh các thao tác bằng ForwardingSimpleBasePlayer

Bạn có thể tuỳ chỉnh một số hành vi của một thực thể Player bằng cách gói thực thể đó trong một lớp con của ForwardingSimpleBasePlayer. Lớp này cho phép bạn chặn các "thao tác" cụ thể, thay vì phải trực tiếp triển khai các phương thức Player. Điều này đảm bảo hành vi nhất quán, ví dụ: play(), pause() và setPlayWhenReady(boolean). Điều này cũng đảm bảo tất cả các thay đổi về trạng thái đều được truyền chính xác đến các thực thể Player.Listener đã đăng ký. Đối với hầu hết các trường hợp sử dụng tuỳ chỉnh, bạn nên ưu tiên ForwardingSimpleBasePlayer hơn ForwardingPlayer vì dễ xảy ra lỗi hơn do các đảm bảo về tính nhất quán này.

Ví dụ: để thêm một số logic tuỳ chỉnh khi quá trình phát bắt đầu hoặc dừng:

Kotlin

class PlayerWithCustomPlay(player: Player) : ForwardingSimpleBasePlayer(player) {
  override fun handleSetPlayWhenReady(playWhenReady: Boolean): ListenableFuture<*> {
    // Add custom logic
    return super.handleSetPlayWhenReady(playWhenReady)
  }
}

Java

public static final class PlayerWithCustomPlay extends ForwardingSimpleBasePlayer {

  public PlayerWithCustomPlay(Player player) {
    super(player);
  }

  @Override
  protected ListenableFuture<?> handleSetPlayWhenReady(boolean playWhenReady) {
    // Add custom logic
    return super.handleSetPlayWhenReady(playWhenReady);
  }
}

Hoặc để không cho phép lệnh SEEK_TO_NEXT (và đảm bảo Player.seekToNext không hoạt động):

Kotlin

class PlayerWithoutSeekToNext(player: Player) : ForwardingSimpleBasePlayer(player) {
  override fun getState(): State {
    val state = super.getState()
    return state
      .buildUpon()
      .setAvailableCommands(
        state.availableCommands.buildUpon().remove(COMMAND_SEEK_TO_NEXT).build()
      )
      .build()
  }

  // We don't need to override handleSeek, because it is guaranteed not to be called for
  // COMMAND_SEEK_TO_NEXT since we've marked that command unavailable.
}

Java

public static final class PlayerWithoutSeekToNext extends ForwardingSimpleBasePlayer {

  public PlayerWithoutSeekToNext(Player player) {
    super(player);
  }

  @Override
  protected State getState() {
    State state = super.getState();
    return state
        .buildUpon()
        .setAvailableCommands(
            state.availableCommands.buildUpon().remove(COMMAND_SEEK_TO_NEXT).build())
        .build();
  }

  // We don't need to override handleSeek, because it is guaranteed not to be called for
  // COMMAND_SEEK_TO_NEXT since we've marked that command unavailable.
}

Tuỳ chỉnh MediaSource

Các ví dụ trước chèn các thành phần tuỳ chỉnh để sử dụng trong quá trình phát tất cả đối tượng MediaItem được truyền đến trình phát. Khi cần tuỳ chỉnh chi tiết, bạn cũng có thể chèn các thành phần tuỳ chỉnh vào từng thực thể MediaSource. Các thực thể này có thể được truyền trực tiếp đến trình phát. Ví dụ sau đây cho thấy cách tuỳ chỉnh ProgressiveMediaSource để sử dụng DataSource.Factory, ExtractorsFactory và LoadErrorHandlingPolicy tuỳ chỉnh:

Kotlin

val mediaSource =
  ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory)
    .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy)
    .createMediaSource(MediaItem.fromUri(streamUri))

Java

ProgressiveMediaSource mediaSource =
    new ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory)
        .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy)
        .createMediaSource(MediaItem.fromUri(streamUri));

Tạo thành phần tuỳ chỉnh

Thư viện này cung cấp các cách triển khai mặc định của những thành phần được liệt kê ở đầu trang này cho các trường hợp sử dụng phổ biến. ExoPlayer có thể sử dụng các thành phần này, nhưng cũng có thể được xây dựng để sử dụng các phương thức triển khai tuỳ chỉnh nếu cần có các hành vi không chuẩn. Sau đây là một số trường hợp sử dụng cho các phương thức triển khai tuỳ chỉnh:

  • Renderer – Bạn nên triển khai Renderer tuỳ chỉnh để xử lý loại nội dung nghe nhìn không được các phương thức triển khai mặc định do thư viện cung cấp hỗ trợ.
  • TrackSelector – Việc triển khai TrackSelector tuỳ chỉnh cho phép nhà phát triển ứng dụng thay đổi cách chọn các bản nhạc do MediaSource hiển thị để mỗi Renderer có sẵn sử dụng.
  • LoadControl – Việc triển khai LoadControl tuỳ chỉnh cho phép nhà phát triển ứng dụng thay đổi chính sách lưu vào vùng đệm của trình phát.
  • Extractor – Nếu bạn cần hỗ trợ một định dạng vùng chứa hiện không được thư viện hỗ trợ, hãy cân nhắc triển khai một lớp Extractor tuỳ chỉnh.
  • MediaSource – Việc triển khai một lớp MediaSource tuỳ chỉnh có thể phù hợp nếu bạn muốn lấy các mẫu nội dung nghe nhìn để cung cấp cho trình kết xuất theo cách tuỳ chỉnh hoặc nếu bạn muốn triển khai hành vi kết hợp MediaSource tuỳ chỉnh.
  • MediaSource.Factory – Việc triển khai MediaSource.Factory tuỳ chỉnh cho phép ứng dụng tuỳ chỉnh cách tạo MediaSource từ MediaItem.
  • DataSource – Gói ngược dòng của ExoPlayer đã chứa một số cách triển khai DataSource cho nhiều trường hợp sử dụng. Bạn nên triển khai lớp DataSource của riêng mình để tải dữ liệu theo cách khác, chẳng hạn như qua một giao thức tuỳ chỉnh, sử dụng ngăn xếp HTTP tuỳ chỉnh hoặc từ bộ nhớ đệm ổn định tuỳ chỉnh.

Khi xây dựng thành phần tuỳ chỉnh, bạn nên làm như sau:

  • Nếu một thành phần tuỳ chỉnh cần báo cáo sự kiện cho ứng dụng, bạn nên làm như vậy bằng cách sử dụng cùng một mô hình với các thành phần ExoPlayer hiện có, ví dụ: sử dụng các lớp EventDispatcher hoặc truyền Handler cùng với trình nghe đến hàm khởi tạo của thành phần.
  • Bạn nên sử dụng các thành phần tuỳ chỉnh có cùng mô hình với các thành phần ExoPlayer hiện có để cho phép ứng dụng định cấu hình lại trong khi phát. Để làm việc này, các thành phần tuỳ chỉnh phải triển khai PlayerMessage.Target và nhận các thay đổi về cấu hình trong phương thức handleMessage. Mã xử lý ứng dụng phải truyền các thay đổi về cấu hình bằng cách gọi phương thức createMessage của ExoPlayer, định cấu hình thông báo và gửi thông báo đó đến thành phần bằng cách sử dụng PlayerMessage.send. Việc gửi thông báo để gửi trên luồng phát đảm bảo rằng các thông báo đó được thực thi theo thứ tự với mọi thao tác khác đang được thực hiện trên trình phát.