12/08/2018, 13:31

Tìm hiểu về ExoPlayer

Chơi nhạc và video là một hoạt động rất phổ biết trên tất cả các thiết bị Android. Và Android framework cung cấp lớp MediaPlayer như là một giải pháp nhanh chóng để thực hiện điều đó. Lớp MediaPlayer cũng cung cấp các api ở mức độ thấp như MediaCodec, AudioTrack và MediaDrm,để ta có thể xây dựng ...

Chơi nhạc và video là một hoạt động rất phổ biết trên tất cả các thiết bị Android. Và Android framework cung cấp lớp MediaPlayer như là một giải pháp nhanh chóng để thực hiện điều đó. Lớp MediaPlayer cũng cung cấp các api ở mức độ thấp như MediaCodec, AudioTrack và MediaDrm,để ta có thể xây dựng một trình player mong muốn.

ExoPlayer là một mã nguồn mở, mức ứng dụng media player được xây dựng ở trên mức api media thấp của Android. Bạn có thể xem mã nguồn của thư viện ExoPlayer lẫn App demo tại đây:

  • ExoPlayerlibrary
  • Demo app

Hướng dẫn này mô tả thư viện ExoPlayer và cách sử dụng nó. Những ưu - khuyết điểm của việc sử dụng ExoPlayer sẽ được đề cập đến, cùng cách sử dụng để play DASH, SmoothStreaming and HLS adaptive streams, cũng như các định dạng FMP4, MP4, M4A, MKV, WebM, MP3, AAC, MPEG-TS, MPEG-PS, OGG, FLV and WAV. ExoPlayer events, messages, customization và DRM support cũng được nói đến.

Pros and cons

ExoPlayer có một số ưu điểm so với MediaPlayer của Android:

  • Hỗ trợ Dynamic Adaptive Streaming thông qua HTTP(DASH) và SmoothStreaming - không được hỗ trợ trên MediaPlayer (nó cũng hỗ trợ HTTP Live Streaming (HLS), FMP4, MP4, M4A, MKV, WebM, MP3, AAC, MPEG-TS, MPEG-PS, OGG, FLV và WAV).
  • Hỗ trợ HLS, chẳng hạn xử lý chính xác thẻ #EXT-X-DISCONTINUITY
  • Khả năng tùy chỉnh và mở rộng cao. ExoPlayer được thiết kế đặc biệt với điều này, cho phép thay thế hoặc tùy chỉnh nhiều thành phần.
  • Dễ dang cập nhật player cùng với ứng dụng của bạn. Bởi vì ExoPlayer là một thư viện được include vào ứng dụng của bạn, bạn có thể dễ dàng kiểm soát các phiên bản cũng như cập nhật mới.
  • Ít vấn đề về thiết bị hơn.

Điều quan trọng cần lưu ý rằng nó cũng có một số nhược điểm:

  • Chuẩn âm thanh và video của ExoPlayer được xây dựng dựa trên Android’s MediaCodec API - được phát hành trên Android 4.1 (API 16), vì vậy các phiên bản hệ điều hành trước đó sẽ không sử dụng được ExoPlayer

Library overview

Cốt lõi của thư viện ExoPlayer là lớp ExoPlayer. Lớp này duy trì trạng thái global của player. Nó duy trì trạng thái global của trình player, thể hiện bản chất của trình player đang được chạy như là làm thế nào dữ liệu media được thu, làm thế nào dữ liệu được buffered hoặc định dạng của nó. Bạn inject các chức năng thông qua phương thức prepare của ExoPlayer dưới dạng các đối tượng TrackRenderer.

ExoPlayer có cung cấp, hỗ trợ audio và video renders mặc định, trong đó có sử dụng các lớp MediaCodec và AudioTrack của Android framework. Cả hai yêu cần injected một đối tượng SampleSource, để từ đó thu nhận mẫu dữ liệu media rồi chạy lại.

Injection của các thành phần là một theme thể hiện xuyên xuốt qua thư viện ExoPlayer. Hình 1 cho thấy mô hình các đối tượng mức cao của một ExoPlayer được cấu hình để chạy streams MP4. Mặc định audio render và video render được injected vào ExoPlayer. Một thể hiện của một lớp được gọi là ExtractorSampleSource được injected vào renderers để cung cấp các mẫu dữ liệu media. DataSource và Extractor là các thể hiện được injected vào ExtractorSampleSource để cho phép tải các mẫu dữ liệu media stream và giải nén các mẫu dữ liệu đã được tải. Trong trường hợp này DefaultUriDataSource và Mp4Extractor được sử dụng để chạy MP4 stream từ URI.

pic1.png

Hình 1: Mô hình đối tượng cho MP4 playbacks sử dụng ExoPlayer

Tóm lại, ExoPlayer được xây dựng bằng cách inject những thành phần cần thiết để cung cấp các chức năng mà nhà phát triển yêu cầu. Mô hình này làm cho nó trở nên dễ dàng khi xây dựng những trình player trong những trường hợp cụ thể, và inject những thành phần tùy chỉnh mong muốn. Các mục tiếp theo sẽ nói về 3 interface quan trọng nhất trong mô hình này: TrackRenderer, SampleSource và DataSource.

TrackRenderer

Một TrackRenderer giữ một loại cụ thể của media như là video, audio hoặc text. Lớp ExoPlayer gọi các phương thức trên các TrackRenderer của nó từ một single playback thread, và bằng cách đó nguyên nhân của mỗi loại được trả về như một điểm global playback. Thư viện ExoPlayer cung cấp MediaCodecVideoTrackRenderer như một thể hiện mặc định cho rendering video, và MediaCodecAudioTrackRenderer cho audio. Cả hai thể hiện đều sử dụng lớp MediaCodec của Android để decode các mẫu dữ liệu media. Thư viện xử lý được tất cả các định dạng audio, video được hỗ trợ cho các thiết bị Android (chi tiết). Thư viện ExoPlayer cũng cung cấp một thể hiện cho rendering text, được gọi là TextTrackRenderer.

Đoạn code dưới đây là một ví dụ cụ thể về các bước thiết kế chính để có thể xây dựng một ExoPlayer chơi video và audio sử dụng các TrackRenderer chuẩn.

    // 1. Instantiate the player.
    player = ExoPlayer.Factory.newInstance(RENDERER_COUNT);
    // 2. Construct renderers.
    MediaCodecVideoTrackRenderer videoRenderer = ...
    MediaCodecAudioTrackRenderer audioRenderer = ...
    // 3. Inject the renderers through prepare.
    player.prepare(videoRenderer, audioRenderer);
    // 4. Pass the surface to the video renderer.
    player.sendMessage(videoRenderer,MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface);
    // 5. Start playback.
    player.setPlayWhenReady(true);
    ...
    player.release(); // Don’t forget to release when done!

Đối với ví dụ hoàn chỉnh, hãy xem lớp PlayerActivity và DemoPlayer trong ExoPlayer demo app để hiểu rõ hơn.

SampleSource

Những thể hiện TrackRenderer chuẩn được cung cấp bởi thư viện, yêu cầu có SampleSource được inject vào. Một đối tượng SampleSource cung cấp thông tin định dang format và mẫu media được rendered. Thư viện ExoPlayer cung cấp các SampleSource cụ thể phù hợp với những trường hợp khác nhau:

  • ExtractorSampleSource: đối với các định dạng FMP4, MP4, M4A, MKV, WebM, MP3, AAC, MPEG-TS, MPEG-PS, OGG, FLV và WAV
  • ChunkSampleSource: đối với DASH và SmoothStreaming
  • HlsSampleSource: đối với HLS

DataSource

Những thể hiện SampleSource được cung cấp bởi thư viện, sử dụng DataSource trong việc tải dữ liệu media. Những thể hiện khác nhau có thể được tìm thấy trong upstream package. Những thể hiện được sử dụng phổ biến nhất là:

  • DefaultUriDataSource: đối với việc chạy media có thể là local hoặc tải qua mạng
  • AssetDataSource: đối với việc chạy media được lưu trữa ở thưu mục assets của ứng dụng

Traditional media playbacks

Thư viện ExoPlayer cung cấp ExtractorSampleSource để chơi các định dạng như FMP4, MP4, M4A, MKV, WebM, MP3, AAC, MPEG-TS, MPEG-PS, OGG, FLV và WAV. Hình 1 đã thể hiện mô hình đối tượng cho một ExoPlayer được xây dựng để chơi MP4 streams. Đoạn mã sau đây sẽ cho thấy các trường hợp TrackRenderer được xây dựng:

Allocator allocator = new DefaultAllocator(BUFFER_SEGMENT_SIZE);
DataSource dataSource = new DefaultUriDataSource(context, null, userAgent);
ExtractorSampleSource sampleSource = new ExtractorSampleSource(uri, dataSource, allocator, BUFFER_SEGMENT_COUNT * BUFFER_SEGMENT_SIZE);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context, sampleSource, MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource, MediaCodecSelector.DEFAULT);

ExtractorSampleSource sẽ tự động tải một cách chính xác các Extractor cho media đang được chơi. Nếu không có Extractor cho media mà bạn muốn chơi, nó sẽ được implement và inject vào chính player của bạn.

ExoPlayer demo app cung cấp một cách đầy đủ của thể hiện này trong mã nguồn ExtractorRendererBuilder. Lớp PlayerActivity sẽ sử dụng nó để chơi những video có sẵn trong demo app.

Adaptive media playbacks

ExoPlayer hỗ trợ adaptive streaming, nó cho phép chất lượng của dữ liệu media được điều chỉnh trong suốt quá trình playback dựa trên điều kiện mạng. DASH, SmoothStreaming và HLS những ví dụng của công nghệ adaptive streaming. Trong cả 3 cái này, media được tải trong một khối nhỏ (thông thường là 2 đến 10 giây). Bất cứ khổi nào của media được yêu cầu, client sẽ chọn một số định dạng thích hợp. Ví dụ, client có thể lựa chọn định dạng chất lượng cao nếu điều kiện mạng là tốt, hoặc chọn định dạng chất lượng thấp nếu điều kiện mạng tệ. Trong cả 3 kỹ thuật, video và audio được truyền riêng biệt.

DASH and SmoothStreaming

ExoPlayer hỗ trợ DASH và SmoothStreaming adaptive playbacks thông qua việc sử dụng ChunkSampleSource, tải các khối dữ liệu của media để từ đó những cá thể mẫu có thể được trích xuất. Mỗi ChunkSampleSource yêu cầu một đối tượng ChunkSource được inject thông qua constructor của nó, ChunkSource chịu trách nhiệm cung cấp các khối media để từ đó có thể tải và đọc các mẫu dữ liệu. Lớp DashChunkSource cung cấp DASH playback sử dụng định dạng chứa FMP4 và WebM. Lớp SmoothStreamingChunkSource cung cấp SmoothStreaming playback sử dụng định dạng FMP4.

Cả hai thể hiện ChunkSource chuẩn đều yêu cầu một FormatEvaluator và một DataSource được inject thông qua constructors. Các đối tượng FormatEvaluator chọn từ các định dạng có sẵn trước mỗi khối đã được tải, và DataSource cung cấp các phương thức để thực sự tải dữ liệu. Cuối cùng, ChunkSampleSource yêu cầu một đối tượng LoadControl để kiểm soát các khổi buffering.

Mô hình đối tượng điển hình cho một ExoPlayer được xây dựng cho DASH adaptive playbacks được thể hiện ở hình 2. Chất lượng video được thay đổi trong thời gian chạy sử dụng adaptive của FormatEvaluator, trong khi âm thanh được chơi mở một mức độ chất lượng nhất định.

pic2.png

Hình 2: Mô hình đối tượng cho DASH adaptive playbacks sử dụng ExoPlayer

Theo dõi đoạn code dưới đây để biết cách video và audio renderers được xây dựng như thế nào

LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
DefaultBandawidthMeter bandawidthMeter = new DefaultBandawidthMeter();

// Build the video renderer.
DataSource videoDataSource = new DefaultUriDataSource(context, bandawidthMeter, userAgent);
ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher,
DefaultDashTrackSelector.newVideoInstance(context, true, false), videoDataSource, new AdaptiveEvaluator(bandawidthMeter), LIVE_EDGE_LATENCY_MS, elapsedRealtimeOffset, null, null, DemoPlayer.TYPE_VIDEO);
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl, VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context, videoSampleSource, MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);

// Build the audio renderer.
DataSource audioDataSource = new DefaultUriDataSource(context, bandawidthMeter, userAgent);
ChunkSource audioChunkSource = new DashChunkSource(manifestFetcher, DefaultDashTrackSelector.newAudioInstance(), audioDataSource, null, LIVE_EDGE_LATENCY_MS, elapsedRealtimeOffset, null, null, DemoPlayer.TYPE_AUDIO);
ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl, AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(audioSampleSource, MediaCodecSelector.DEFAULT);

Trong đoạn code, manifestFetcher là một đối tượng chịu trách nhiệm cho việc tải DASH manifest định nghĩa media. videoAdaptationSetIndex và audioAdaptationSetIndex tương ứng với video và audio.

Tham khảo cụ thể hơn trong ExoPlayer demo app, DASH và SmoothStreaming được thể hiện rõ trong lớp DashRendererBuilder và SmoothStreamingRendererBuilder.

HLS

ExoPlayer hỗ trợ HLS adaptive playbacks thông qua sử dụng HlsSampleSource - tải các khối dữ liệu của media, để từ đó các cá thể mẫu được trích xuất. Một HlsSampleSource yêu cầu một HlsChunkSource được inject thông qua constructor, HlsChunkSource có trách nhiệm cung cấp các khối media để từ đó tải và đọc các mẫu. Một HlsChunkSource yêu cầu một DataSource được inject thông qua constructor, thông qua đó dữ liệu có thể được tải.

Mô hình đối tượng điển hình cho một ExoPlayer được xây dựng cho HLS adaptive playbacks được thể hiện dưới hình 3.

pic3.png Hình 3: Mô hình đối tượng cho HLS playbacks sử dụng ExoPlayer

Theo dõi đoạn code bên dưới để hiểu rõ hơn cách xây dựng video and audio renderers.

LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
DefaultBandawidthMeter bandawidthMeter = new DefaultBandawidthMeter();
PtsTimestampAdjusterProvider timestampAdjusterProvider = new PtsTimestampAdjusterProvider();
DataSource dataSource = new DefaultUriDataSource(context, bandawidthMeter, userAgent);
HlsChunkSource chunkSource = new HlsChunkSource(true /* isMaster */, dataSource, url, manifest, DefaultHlsTrackSelector.newDefaultInstance(context), bandawidthMeter, timestampAdjusterProvider, HlsChunkSource.ADAPTIVE_MODE_SPLICE);
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, loadControl, MAIN_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context, sampleSource, MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource, MediaCodecSelector.DEFAULT);

Player events

Trong suốt quá trình chơi, ứng dụng của bạn có thể lắng nghe sự kiện được tạo ra bởi ExoPlayer, những sự kiện đó sẽ được thể hiện qua trạng thái của player. Những sự kiện này giúp theo dõi trạng thái của player qua đó giúp ứng dụng của bản điều khiển một cách linh hoạt hơn. Nhiều thành phần của ExoPlayer cũng có các event ở mức độ thấp, giúp có thể theo dõi được hiệu xuất của trình play.

High level events

ExoPlayer cho phép các trường của ExoPlayer.Listener có thể thêm vào hoặc remove bằng cách sử dụng 2 phương thức addListener() và removeListener(). Đăng ký listeners để nhận được thông báo về những thay đổi trạng thái play state hoặc những thông báo lỗi khi play. Để biết thêm về các trạng thái playback có thể tìm hiểu kỹ hơn trong mã nguồn ExoPlayer.

Low level events

Bên cạnh listeners mức độ cao, rất nhiều thành phần được cung cấp bởi ExoPlayer, cho phép người dùng lắng nghe sự kiện. Ví dụ MediaCodecVideoTrackRenderer có constructors MediaCodecVideoTrackRenderer.EventListener. Trong ExoPlayer demo app, DemoPlayer đóng vai trò lắng nghe rất nhiều thành phần và chuyển tiếp event cho PlayerActivity, bằng cách này cho phép PlayerActivity thay đổi điều chỉnh kích thước của surface phù hợp với các video được phát:

@Override
public void onVideoSizeChanged(int awidth, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
  surfaceView.setVideoWidthHeightRatio(
      height == 0 ? 1 : (awidth * pixelWidthAspectRatio) / height);
}

Lưu ý rằng bạn phải vượt qua một đối tượng Handler - quyết định các luồng, mà trên đó các phương thức được gọi đến. Trong hầu hết các trường hợp, nên sử dụng một Handler liên quan đến luồng chính.

Việc lắng nghe các sự kiện giúp ích cho việc điều chỉnh UI phù hợp dựa trên các sự kiện của player. Lắng nghe các sự kiện thành phần cũng giúp ích cho việc logg. Ví dụ MediaCodecVideoTrackRenderer cho phép lắng nghe số khung hình của video. Đối với một một nhà phát triển, điều đó là cần thiết trong việc theo dõi hiệu xuất của một trình player.

Nhiều thành phần cũng thông báo các sự kiện khi xuất hiện lỗi, những lỗi này có thể có hoặc không khiến cho playback fail. Những lỗi không khiến cho playback fail nhưng nó cũng có thể ảnh hưởng đến hiệu năng chạy của player. Lưu ý rằng một ExoPlayer luôn thông báo những sự kiện ở mức cao gây lên playback fail.

Sending messages to components

Một số thành phần của ExoPlayer cho phép thay đổi cấu hình khi đang phát. Theo quy ước, bạn thực hiện các thay đổi qua các messages không đồng bộ thông qua các thành phần của ExoPlayer. Cách tiếp cận này đảm bảo rằng cả thread và cấu hình thay đổi được thực hiện theo thứ tự với bất kỳ hoạt động khác đang được thực hiện trên player.

Việc sử dụng phổ biến nhất của message là passing qua một surface MediaCodecVideoTrackRenderer:

player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface);

Lưu ý nếu surface bị destroy bởi phương thức SurfaceHolder.Callback.surfaceDestroyed(), bạn phải gửi message sử dụng một blocking.

player.blockingSendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, null);

Customization

Một trong những đặc điểm nổi bật của ExoPlayer hơn MediaPlayer của Android là khả năng tùy biến và mở rộng để phù hợp với nhiều mục đích khác nhau. Thư viện ExoPlayer được đặc biệt thiết kế theo điều này, xác định những lớp trừ tượng cơ bản và các interfaces để cung cấp cho nhà phát triển có thể dễ dàng thay đổi. Dưới đây là một số trường hợp cho việc xây dựng các thành phần tùy chỉnh:

  • TrackRenderer: bạn có thể sử dụng một TrackRenderer tùy chỉnh để xử lý những loại media khác với video và audio. Lớp TextTrackRenderer trong thư viện ExoPlayer là một ví dụ về việc làm thế nào để tạo một renderer tùy chỉnh.
  • Extractor: nếu bạn cần hỗ trợ một định dạng mà không được hỗ trợ bởi thư viện ExoPlayer, hãy xem xét đến một lớp Extractor tùy chỉnh - lớp có thể sau đó được sử dụng cùng với ExtractorSampleSource để chơi media loại đó.
  • SampleSource: thể hiện một lớp SampleSource tùy chỉnh có thể thích hợp nếu bạn muốn lấy mẫu media để thực hiện một renderer tùy chỉnh.
  • FormatEvaluator: đổi với DASH và SmoothStreaming playbacks, thư viện ExoPlayer cung cấp FormatEvaluator.AdaptiveEvaluator như một tài liệu tham khảo thực hiện chuyển đổi giữa các định dạng chất lượng khác nhau dựa trên băng thông của mạng. Nhà phát triển được khuyến khích triển khai một FormatEvaluator riêng phù hợp với nhu cầu riêng.
  • DataSource: ExoPlayer’s upstream package đã có chứa một số thể hiện của DataSource cho một số trường hợp khác nhau. Bạn có thể thực hiện một lớp DataSource riêng để tải dữ liệu theo một cách nào đó riêng biệt, chẳng hạn như một protocol tùy biến, hoặc một HTTP tùy biến.

Customization guidelines

  • Nếu một thành phần tùy chỉnh cần thông báo event ngược trở lại cho ứng dụng, chúng tôi khuyên bạn không nên sử dụng các mô hình tương tự như các thành phần của ExoPlayer - nơi mà các event listener được pass cùng với nhau thông qua một thành phần Handler.
  • Chúng tôi đề nghị rằng các thành phần tùy chỉnh sử dụng các mô hình tương tự như các thành phần của ExoPlayer để cho phép cấu hình lại trong suốt quá trình playback như gửi một thành phần message. Để thực hiện điều này, bạn nên thực hiện một ExoPlayerComponent và nhận những thay đổi cấu hình trong phương thức handleMessage(). Ứng dụng của bạn nên pass những thay đổi cấu hình bằng cách gọi phương thức sendMessage() và blockingSendMessage() của ExoPlayer.

Digital Rights Management

Từ Android 4.3 (api 18) trở lên, ExoPlayer hỗ trợ Digital Rights Managment (DRM) protected playback. Để phát nội dụng được bảo vê DRM bằng ExoPlayer, ứng dụng của bạn phải inject một DrmSessionManager vào trong MediaCodecVideoTrackRenderer. Một đối tượng DrmSessionManager có trách nhiệm cung cấp đối tượng MediaCrypto cần thiết để giải mã, cũng như đảm bảo decryption keys có sẵn cho các DRM module được sử dụng.

Thư viện ExoPlayer cung cấp một thể hiện mặc định của DrmSessionManager được gọi là StreamingDrmSessionManager - trong đó sử dụng MediaDrm. Session manager hỗ trợ bất kỳ DRM scheme mà một thành phần modular DRM tồn tại trên thiết bị.Tất cả các thiết bị Android đều được yêu cầu để hỗ trợ Widevine modular DRM (với L3). Một số thiết bị hỗ trợ đề án bổ sung như là PlayReady.

Lớp StreamingDrmSessionManager yêu cầu một MediaDrmCallback được inject.

0