Tuỳ chỉnh

Phần cốt lõi của thư viện ExoPlayer là giao diện Player. Player cho thấy chức năng của trình phát nội dung đa phương tiệ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 bộ đệm, phát, tạm dừng và tua. Phương thức triển khai mặc định ExoPlayer được thiết kế để đưa ra vài giả định về (và do đó áp dụng ít 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ữ nội dung nghe nhìn, cũng như cách nội dung nghe nhìn hiển thị. Thay vì triển khai việc tải và kết xuất nội dung nghe nhìn trực tiếp, các phương thức triển khai ExoPlayer 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 phổ biến cho tất cả cách triển khai ExoPlayer là:

  • Các thực thể MediaSource xác định nội dung nghe nhìn sẽ được phát, tải nội dung nghe nhìn và có thể đọc nội dung nghe nhìn đã tải từ đó. Thực thể MediaSource được tạo từ MediaItem bởi MediaSource.Factory bên trong trình phát. Danh sách phát cũng có thể được chuyển trực tiếp đế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.
  • Các 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.
  • Các thực thể Renderer kết xuất từng thành phần của nội dung nghe nhìn. Các đối tượng này sẽ được chèn vào khi tạo trình phát.
  • TrackSelector chọn các kênh do MediaSource cung cấp để mỗi Renderer có sẵn sử dụng. TrackSelector sẽ được chèn khi tạo trình phát.
  • LoadControl kiểm soát thời điểm MediaSource lưu vào vùng đệm nhiều nội dung nghe nhìn hơn 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 các lần phát trực tiếp để cho phép trình phát ở gần mức chênh 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 hiện có trong thư viện. Phương thức triển khai mặc định của một số uỷ quyền thành phần hoạt động với 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ề cách tuỳ chỉnh trình phát bằng cách chèn các thành phần được mô tả ở bên dưới.

Định cấu hình ngăn xếp mạng

Chúng ta có một trang về cách tuỳ chỉnh ngăn xếp 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ề cách lưu vào bộ nhớ đệm tạm thời ngay lập tứctải nội dung nghe nhìn xuống.

Tùy chỉnh các 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 và phản hồi HTTP. Bạn nê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 khúc nội dung nghe nhìn.

Ví dụ sau 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ên, 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 khắc phục cho mọi tương tác với nguồn HTTP.

Để có phương pháp chi tiết hơn, bạn có thể chèn hành vi đúng thời điểm bằng cách sử dụng ResolvingDataSource. Đoạn mã sau đây cho biết cách chèn tiêu đề của yêu cầu ngay trước khi tương tác với một 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 sửa đổi URI kịp thời, 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ụ: có thể một ứng dụng sẽ muốn thất bại nhanh chóng 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 khoảng thời gian người chơi chờ giữa mỗi lần thử lại. Đoạn mã sau đây cho biết cách triển khai logic thời gian đợi tuỳ chỉnh:

Kotlin

val loadErrorHandlingPolicy: LoadErrorHandlingPolicy =
  object : DefaultLoadErrorHandlingPolicy() {
    override fun getRetryDelayMsFor(loadErrorInfo: 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(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ể sử 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 tiến bộ. Bạn có thể đặt các giá trị 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 luồng MP3 dựa trên chỉ mục.

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 chế độ tua với tốc độ bit không đổi

Đối với luồng MP3, ADTS và AMR, bạn có thể bật tính năng tìm kiếm 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 những cờ này cho các trình trích xuất riêng lẻ bằ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ả các trình trích xuất hỗ trợ tốc độ này, hãy sử dụng DefaultExtractorsFactory.setConstantBitrateSeekingEnabled.

Kotlin

val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)

Java

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

Sau đó, ExtractorsFactory có thể được chèn thông qua DefaultMediaSourceFactory như mô tả để tuỳ chỉnh các cờ trích xuất ở trên.

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

Xếp hàng 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, vận hành các thực thể MediaCodecchế độ 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. Khi bật tính năng này, bạn có thể giảm khung hình bị rớt và tình trạng chạy âm thanh dưới mức.

Tính năng xếp hàng bộ đệm không đồng bộ được bật theo mặc định trên các thiết bị chạy Android 12 (API cấp 31) trở lên. Đồng thời, bạn có thể bật tính năng này theo cách thủ công kể từ Android 6.0 (API cấp 23). Hãy cân nhắc bật tính năng này cho các thiết bị cụ thể mà bạn quan sát thấy khung hình bị rớt hoặc âm thanh bị giảm, đặ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 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 tạo thực thể cho trình kết xuất trực tiếp, hãy truyền AsynchronousMediaCodecAdapter.Factory đến hàm khởi tạo MediaCodecVideoRendererMediaCodecAudioRenderer.

Chặn các lệnh gọi phương thức bằng ForwardingPlayer

Bạn có thể tuỳ chỉnh một số hành vi của thực thể Player bằng cách gói thực thể đó vào một lớp con của ForwardingPlayer và ghi đè các phương thức để thực hiện bất kỳ thao tác nào sau đây:

  • Truy cập các tham số trước khi chuyển các tham số đó cho Player uỷ quyền.
  • Truy cập vào giá trị trả về từ thực thể đại diện Player trước khi trả về giá trị đó.
  • Triển khai lại phương thức này hoàn toàn.

Khi ghi đè các phương thức ForwardingPlayer, điều quan trọng là phải đảm bảo quá trình triển khai vẫn phải nhất quán và tuân thủ với giao diện Player, đặc biệt là khi xử lý các phương thức có hành vi giống hệt hoặc liên quan. Ví dụ:

  • Nếu muốn ghi đè mọi thao tác "play", bạn cần ghi đè cả ForwardingPlayer.playForwardingPlayer.setPlayWhenReady, vì phương thức gọi sẽ dự kiến hành vi của các phương thức này sẽ giống hệt nhau khi playWhenReady = true.
  • Nếu muốn thay đổi mức tăng tua đi, bạn cần ghi đè cả ForwardingPlayer.seekForward để thực hiện lệnh tìm kiếm với mức tăng tuỳ chỉnh và ForwardingPlayer.getSeekForwardIncrement để báo cáo chính xác mức tăng tuỳ chỉnh cho phương thức gọi.
  • Nếu muốn kiểm soát những Player.Commands mà một thực thể trình phát quảng cáo, bạn phải ghi đè cả Player.getAvailableCommands()Player.isCommandAvailable(), đồng thời theo dõi lệnh gọi lại Player.Listener.onAvailableCommandsChanged() để nhận thông báo về những thay đổi đến từ trình phát cơ bản.

Tuỳ chỉnh MediaSource

Các ví dụ ở trên sẽ chèn các thành phần tuỳ chỉnh để sử dụng trong quá trình phát mọi đố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 riêng lẻ. Sau đó, những thực thể này có thể được truyền trực tiếp đến trình phát. Ví dụ bên dưới cho biết cách tuỳ chỉnh ProgressiveMediaSource để sử dụng DataSource.Factory, ExtractorsFactoryLoadErrorHandlingPolicy 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 cung cấp các phương thức triển khai mặc định cho các 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 tạo để sử dụng các phương pháp triển khai tuỳ chỉnh nếu bắt buộc phải có các hành vi không chuẩn. Một số trường hợp sử dụng cho phương pháp triển khai tuỳ chỉnh là:

  • Renderer – Bạn nên triển khai một Renderer tuỳ chỉnh để xử lý loại nội dung đa phương tiện không được hỗ trợ trong các phương thức triển khai mặc định do thư viện cung cấp.
  • 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 các bản nhạc do MediaSource hiển thị được chọn để sử dụng theo từng Renderer có sẵn.
  • 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 bộ đệm của người chơi.
  • 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 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 để cấp dữ liệu 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 tổng 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 thượng nguồn của ExoPlayer đã chứa một số phương thức triển khai DataSource cho các trường hợp sử dụng khác nhau. Bạn nên triển khai lớp DataSource của mình để tải dữ liệu theo cách khác, chẳng hạn như qua giao thức tuỳ chỉnh, sử dụng ngăn xếp HTTP tuỳ chỉnh hoặc từ bộ nhớ đệm cố định tuỳ chỉnh.

Khi tạo thành phần tuỳ chỉnh, bạn nên:

  • Nếu một thành phần tuỳ chỉnh cần báo cáo sự kiện trở lại ứ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ó, chẳng hạn như sử dụng các lớp EventDispatcher hoặc truyền một Handler cùng với một trình nghe đến hàm khởi tạo của thành phần.
  • Các thành phần tuỳ chỉnh nên sử dụng mô hình giống 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 điều này, các thành phần tuỳ chỉnh phải triển khai PlayerMessage.Target và nhận được các thay đổi về cấu hình trong phương thức handleMessage. Mã ứ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 PlayerMessage.send. Việc gửi các thông báo sẽ được gửi trên luồng phát sẽ đảm bảo rằng các thông báo đó được thực thi đúng với mọi thao tác khác đang được thực hiện trên trình phát.